diff options
| author | chm@openerp.com <> | 2014-04-23 17:48:52 +0200 |
|---|---|---|
| committer | chm@openerp.com <> | 2014-04-23 17:48:52 +0200 |
| commit | ee7ff4bd07d8d84ed2ebb0e2e3f5eb437f158e5b (patch) | |
| tree | 25577845139d390f9bf27e0237d18842aeace02b /addons/mass_mailing | |
| parent | b1bc68d8eb374f30173e03e8048d1c3588a00d4b (diff) | |
| parent | 1657ffb8e7d95ef5f94e59121737abb068371b66 (diff) | |
[MERGE] from trunk
bzr revid: chm@openerp.com-20140423154852-gzegxqrzb2sj01zl
Diffstat (limited to 'addons/mass_mailing')
30 files changed, 1847 insertions, 1191 deletions
diff --git a/addons/mass_mailing/__init__.py b/addons/mass_mailing/__init__.py index 9dadc456842..175383c6644 100644 --- a/addons/mass_mailing/__init__.py +++ b/addons/mass_mailing/__init__.py @@ -1,26 +1,5 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> -# -############################################################################## -import mass_mailing -import mail_mail -import mail_thread +import models import wizard import controllers diff --git a/addons/mass_mailing/__openerp__.py b/addons/mass_mailing/__openerp__.py index 92f8b1fc3e8..91b57ac5b5a 100644 --- a/addons/mass_mailing/__openerp__.py +++ b/addons/mass_mailing/__openerp__.py @@ -21,26 +21,33 @@ { 'name': 'Mass Mailing Campaigns', + 'summary': 'Design, send and track emails', 'description': """ Easily send mass mailing to your leads, opportunities or customers. Track marketing campaigns performance to improve conversion rates. Design professional emails and reuse templates in a few clicks. """, - 'version': '1.0', + 'version': '2.0', 'author': 'OpenERP', 'website': 'http://www.openerp.com', 'category': 'Marketing', 'depends': [ 'mail', 'email_template', + 'marketing', 'web_kanban_gauge', 'web_kanban_sparkline', + 'website_mail', ], 'data': [ - 'mail_data.xml', + 'data/mail_data.xml', + 'data/mass_mailing_data.xml', 'wizard/mail_compose_message_view.xml', - 'wizard/mail_mass_mailing_create_segment.xml', - 'mass_mailing_view.xml', + 'wizard/test_mailing.xml', + 'views/mass_mailing.xml', + 'views/res_config.xml', + 'views/res_partner.xml', + 'views/email_template.xml', 'security/ir.model.access.csv', ], 'js': [ @@ -48,10 +55,11 @@ professional emails and reuse templates in a few clicks. ], 'qweb': [], 'css': [ - 'static/src/css/mass_mailing.css' + 'static/src/css/mass_mailing.css', + 'static/src/css/email_template.css' ], 'demo': [ - 'mass_mailing_demo.xml', + 'data/mass_mailing_demo.xml', ], 'installable': True, 'auto_install': False, diff --git a/addons/mass_mailing/controllers/main.py b/addons/mass_mailing/controllers/main.py index 046084621c7..7396c02d453 100644 --- a/addons/mass_mailing/controllers/main.py +++ b/addons/mass_mailing/controllers/main.py @@ -1,11 +1,42 @@ +import werkzeug + from openerp import http, SUPERUSER_ID from openerp.http import request + class MassMailController(http.Controller): + @http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='none') - def track_mail_open(self, mail_id): + def track_mail_open(self, mail_id, **post): """ Email tracking. """ mail_mail_stats = request.registry.get('mail.mail.statistics') mail_mail_stats.set_opened(request.cr, SUPERUSER_ID, mail_mail_ids=[mail_id]) - return "data:image/gif;base64,R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" + response = werkzeug.wrappers.Response() + response.mimetype = 'image/gif' + response.set_data('R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='.decode('base64')) + return response + + @http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', auth='none') + def mailing(self, mailing_id, email=None, res_id=None, **post): + cr, uid, context = request.cr, request.uid, request.context + MassMailing = request.registry['mail.mass_mailing'] + mailing_ids = MassMailing.exists(cr, SUPERUSER_ID, [mailing_id], context=context) + if not mailing_ids: + return 'KO' + mailing = MassMailing.browse(cr, SUPERUSER_ID, mailing_ids[0], context=context) + if mailing.mailing_model == 'mail.mass_mailing.contact': + list_ids = [l.id for l in mailing.contact_list_ids] + record_ids = request.registry[mailing.mailing_model].search(cr, SUPERUSER_ID, [('list_id', 'in', list_ids), ('id', '=', res_id), ('email', 'ilike', email)], context=context) + request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context) + else: + email_fname = None + if 'email_from' in request.registry[mailing.mailing_model]._all_columns: + email_fname = 'email_from' + elif 'email' in request.registry[mailing.mailing_model]._all_columns: + email_fname = 'email' + if email_fname: + record_ids = request.registry[mailing.mailing_model].search(cr, SUPERUSER_ID, [('id', '=', res_id), (email_fname, 'ilike', email)], context=context) + if 'opt_out' in request.registry[mailing.mailing_model]._all_columns: + request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context) + return 'OK' diff --git a/addons/mass_mailing/mail_data.xml b/addons/mass_mailing/data/mail_data.xml index a1a0e404fe3..a1a0e404fe3 100644 --- a/addons/mass_mailing/mail_data.xml +++ b/addons/mass_mailing/data/mail_data.xml diff --git a/addons/mass_mailing/data/mass_mailing_data.xml b/addons/mass_mailing/data/mass_mailing_data.xml new file mode 100644 index 00000000000..6bd1df79a72 --- /dev/null +++ b/addons/mass_mailing/data/mass_mailing_data.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<openerp> + <data noupdate="1"> + + <!-- After installation of the module, open the related menu --> + <record id="action_client_marketing_menu" model="ir.actions.client"> + <field name="name">Open Marketing Menu</field> + <field name="tag">reload</field> + <field name="params" eval="{'menu_id': ref('base.marketing_menu')}"/> + </record> + <record id="base.open_menu" model="ir.actions.todo"> + <field name="action_id" ref="action_client_marketing_menu"/> + <field name="state">open</field> + </record> + + <!-- Group to manage campaigns --> + <record id="group_mass_mailing_campaign" model="res.groups"> + <field name="name">Manage Mass Mailing Campaigns</field> + <field name="category_id" ref="base.module_category_hidden"/> + </record> + + <!-- Default stages of mass mailing campaigns --> + <record id="campaign_stage_1" model="mail.mass_mailing.stage"> + <field name="name">Schedule</field> + <field name="sequence">10</field> + </record> + <record id="campaign_stage_2" model="mail.mass_mailing.stage"> + <field name="name">Design</field> + <field name="sequence">20</field> + </record> + <record id="campaign_stage_3" model="mail.mass_mailing.stage"> + <field name="name">Sent</field> + <field name="sequence">30</field> + </record> + + </data> +</openerp>
\ No newline at end of file diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml new file mode 100644 index 00000000000..569feec9c88 --- /dev/null +++ b/addons/mass_mailing/data/mass_mailing_demo.xml @@ -0,0 +1,146 @@ +<?xml version="1.0"?> +<openerp> + <data noupdate="1"> + + <record id="mass_mail_attach_1" model="ir.attachment"> + <field name="datas">bWlncmF0aW9uIHRlc3Q=</field> + <field name="datas_fname">SampleDoc.doc</field> + <field name="name">SampleDoc.doc</field> + </record> + + <!-- Create mailing lists --> + <record id="mass_mail_list_1" model="mail.mass_mailing.list"> + <field name="name">Imported Contacts</field> + </record> + + <!-- Create Contacts --> + <record id="mass_mail_contact_1" model="mail.mass_mailing.contact"> + <field name="name">Aristide Antario</field> + <field name="email">aa@example.com</field> + <field name="list_id" ref="mass_mailing.mass_mail_list_1"/> + </record> + <record id="mass_mail_contact_2" model="mail.mass_mailing.contact"> + <field name="name">Beverly Bridge</field> + <field name="email">bb@example.com</field> + <field name="list_id" ref="mass_mailing.mass_mail_list_1"/> + </record> + <record id="mass_mail_contact_3" model="mail.mass_mailing.contact"> + <field name="name">Carol Cartridge</field> + <field name="email">cc@example.com</field> + <field name="list_id" ref="mass_mailing.mass_mail_list_1"/> + <field name="opt_out" eval="True"/> + </record> + + <!-- Create campaign and mailings --> + <record id="mass_mail_category_1" model="mail.mass_mailing.category"> + <field name="name">Marketing</field> + </record> + <record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign"> + <field name="name">Newsletter</field> + <field name="stage_id" ref="mass_mailing.campaign_stage_1"/> + <field name="user_id" eval="ref('base.user_root')"/> + <field name="category_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_category_1')])]"/> + </record> + + <record id="mass_mail_1" model="mail.mass_mailing"> + <field name="name">First Newsletter</field> + <field name="state">done</field> + <field name="sent_date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/> + <field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/> + <field name="mailing_model">res.partner</field> + <field name="mailing_domain">[('customer', '=', True)]</field> + <field name="reply_to_mode">email</field> + <field name="reply_to"><![CDATA[Info <info@yourcompany.example.com>]]></field> + <field name="body_html"><![CDATA[<div data-snippet-id="big-picture" style="padding:0px; margin:0px"> +<table cellpadding="0" cellspacing="0" style="margin:10px 0px 0px;vertical-align:top;padding:0px;font-family:arial;font-size:12px;color:rgb(51,51,51)"> + <tbody> + <tr> + <td style="width:600px" valign="top"> + <h2 style="text-align: center; padding:0px 5px">A Punchy Headline</h2> + </td> + </tr> + <tr> + <td style="width:600px" valign="top"><img src="/website/static/src/img/big_picture.png" style="display:block;border:none;min-height:250px;margin:0 auto;" width="500"></td> + </tr> + <tr> + <td style="width:600px" valign="top"> + <p style="text-align: center; overflow:hidden"></p> + + <h3 style="text-align: center; padding:0px 5px">A Small Subtitle for ${object.name}</h3> + + <p></p> + + <p style="text-align: center; overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p> + </td> + </tr> + </tbody> +</table> +</div> +<div data-snippet-id="three-columns" style="padding:0px; margin:0px"> +<table cellpadding="0" cellspacing="0" style="margin:10px 0px 0px;vertical-align:top;padding:0px;font-family:arial;font-size:12px;color:rgb(51,51,51)"> + <tbody> + <tr> + <td style="width:300px" valign="top"><img src="/website/static/src/img/desert_thumb.jpg" style="display:block;border:none;min-height:50px" width="275"></td> + <td style="width:300px" valign="top"><img src="/website/static/src/img/deers_thumb.jpg" style="display:block;border:none;min-height:50px" width="275"></td> + </tr> + <tr> + <td style="width:300px" valign="top"> + <h3 style="text-align: center; padding:0px 5px">Feature One</h3> + + <p style="overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p> + </td> + <td style="width:300px" valign="top"> + <h3 style="text-align: center; padding:0px 5px">Feature Two</h3> + + <p style="overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p> + </td> + </tr> + </tbody> +</table> +</div>]]></field> + <field name="attachment_ids" eval="[(4, ref('mass_mail_attach_1'))]"/> + </record> + <record id="mass_mail_2" model="mail.mass_mailing"> + <field name="name">Second Newsletter</field> + <field name="state">test</field> + <field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/> + <field name="mailing_model">res.partner</field> + <field name="mailing_domain">[('customer', '=', True)]</field> + <field name="reply_to_mode">email</field> + <field name="reply_to"><![CDATA[Info <info@yourcompany.example.com>]]></field> + </record> + + <record id="mass_mail_email_1" model="mail.mail.statistics"> + <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> + <field name="message_id">1111000@OpenERP.com</field> + <field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/> + <field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/> + <field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/> + </record> + <record id="mass_mail_email_2" model="mail.mail.statistics"> + <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> + <field name="message_id">1111001@OpenERP.com</field> + <field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/> + <field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/> + <field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/> + </record> + <record id="mass_mail_email_3" model="mail.mail.statistics"> + <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> + <field name="message_id">1111002@OpenERP.com</field> + <field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/> + <field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/> + </record> + <record id="mass_mail_email_4" model="mail.mail.statistics"> + <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> + <field name="message_id">1111003@OpenERP.com</field> + <field name="exception" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/> + </record> + <record id="mass_mail_email_5" model="mail.mail.statistics"> + <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> + <field name="message_id">1111004@OpenERP.com</field> + <field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/> + <field name="bounced" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/> + </record> + + </data> +</openerp> diff --git a/addons/mass_mailing/mass_mailing.py b/addons/mass_mailing/mass_mailing.py deleted file mode 100644 index e7583d8e739..00000000000 --- a/addons/mass_mailing/mass_mailing.py +++ /dev/null @@ -1,369 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> -# -############################################################################## - -from datetime import datetime -from dateutil import relativedelta - -from openerp import tools -from openerp.tools.translate import _ -from openerp.osv import osv, fields - - -class MassMailingCampaign(osv.Model): - """Model of mass mailing campaigns. - """ - _name = "mail.mass_mailing.campaign" - _description = 'Mass Mailing Campaign' - # number of embedded mailings in kanban view - _kanban_mailing_nbr = 4 - - def _get_statistics(self, cr, uid, ids, name, arg, context=None): - """ Compute statistics of the mass mailing campaign """ - results = dict.fromkeys(ids, False) - for campaign in self.browse(cr, uid, ids, context=context): - results[campaign.id] = { - 'sent': len(campaign.statistics_ids), - # delivered: shouldn't be: all mails - (failed + bounced) ? - 'delivered': len([stat for stat in campaign.statistics_ids if not stat.bounced]), # stat.state == 'sent' and - 'opened': len([stat for stat in campaign.statistics_ids if stat.opened]), - 'replied': len([stat for stat in campaign.statistics_ids if stat.replied]), - 'bounced': len([stat for stat in campaign.statistics_ids if stat.bounced]), - } - return results - - def _get_mass_mailing_kanban_ids(self, cr, uid, ids, name, arg, context=None): - """ Gather data about mass mailings to display them in kanban view as - nested kanban views is not possible currently. """ - results = dict.fromkeys(ids, '') - for campaign in self.browse(cr, uid, ids, context=context): - mass_mailing_results = [] - for mass_mailing in campaign.mass_mailing_ids[:self._kanban_mailing_nbr]: - mass_mailing_object = {} - for attr in ['name', 'sent', 'delivered', 'opened', 'replied', 'bounced']: - mass_mailing_object[attr] = getattr(mass_mailing, attr) - mass_mailing_results.append(mass_mailing_object) - results[campaign.id] = mass_mailing_results - return results - - _columns = { - 'name': fields.char( - 'Campaign Name', required=True, - ), - 'user_id': fields.many2one( - 'res.users', 'Responsible', - required=True, - ), - 'mass_mailing_ids': fields.one2many( - 'mail.mass_mailing', 'mass_mailing_campaign_id', - 'Mass Mailings', - ), - 'mass_mailing_kanban_ids': fields.function( - _get_mass_mailing_kanban_ids, - type='text', string='Mass Mailings (kanban data)', - help='This field has for purpose to gather data about mass mailings ' - 'to display them in kanban view as nested kanban views is not ' - 'possible currently', - ), - 'statistics_ids': fields.one2many( - 'mail.mail.statistics', 'mass_mailing_campaign_id', - 'Sent Emails', - ), - 'color': fields.integer('Color Index'), - # stat fields - 'sent': fields.function( - _get_statistics, - string='Sent Emails', - type='integer', multi='_get_statistics' - ), - 'delivered': fields.function( - _get_statistics, - string='Delivered', - type='integer', multi='_get_statistics', - ), - 'opened': fields.function( - _get_statistics, - string='Opened', - type='integer', multi='_get_statistics', - ), - 'replied': fields.function( - _get_statistics, - string='Replied', - type='integer', multi='_get_statistics' - ), - 'bounced': fields.function( - _get_statistics, - string='Bounced', - type='integer', multi='_get_statistics' - ), - } - - _defaults = { - 'user_id': lambda self, cr, uid, ctx=None: uid, - } - - def launch_mass_mailing_create_wizard(self, cr, uid, ids, context=None): - ctx = dict(context) - ctx.update({ - 'default_mass_mailing_campaign_id': ids[0], - }) - return { - 'name': _('Create a Mass Mailing for the Campaign'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'mail.mass_mailing.create', - 'views': [(False, 'form')], - 'view_id': False, - 'target': 'new', - 'context': ctx, - } - - -class MassMailing(osv.Model): - """ MassMailing models a wave of emails for a mass mailign campaign. - A mass mailing is an occurence of sending emails. """ - - _name = 'mail.mass_mailing' - _description = 'Wave of sending emails' - # number of periods for tracking mail_mail statistics - _period_number = 6 - _order = 'date DESC' - - def __get_bar_values(self, cr, uid, id, obj, domain, read_fields, value_field, groupby_field, context=None): - """ Generic method to generate data for bar chart values using SparklineBarWidget. - This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). - - :param obj: the target model (i.e. crm_lead) - :param domain: the domain applied to the read_group - :param list read_fields: the list of fields to read in the read_group - :param str value_field: the field used to compute the value of the bar slice - :param str groupby_field: the fields used to group - - :return list section_result: a list of dicts: [ - { 'value': (int) bar_column_value, - 'tootip': (str) bar_column_tooltip, - } - ] - """ - date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT).date() - section_result = [{'value': 0, - 'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'), - } for i in range(0, self._period_number)] - group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) - field_col_info = obj._all_columns.get(groupby_field.split(':')[0]) - pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT - for group in group_obj: - group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date() - timedelta = relativedelta.relativedelta(group_begin_date, date_begin) - section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)} - return section_result - - def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None): - """ Get the daily statistics of the mass mailing. This is done by a grouping - on opened and replied fields. Using custom format in context, we obtain - results for the next 6 days following the mass mailing date. """ - obj = self.pool['mail.mail.statistics'] - res = {} - for id in ids: - res[id] = {} - date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT) - date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1) - date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) - date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) - domain = [('mass_mailing_id', '=', id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)] - res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened:day', context=context) - domain = [('mass_mailing_id', '=', id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)] - res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied:day', context=context) - return res - - def _get_statistics(self, cr, uid, ids, name, arg, context=None): - """ Compute statistics of the mass mailing campaign """ - results = dict.fromkeys(ids, False) - for mass_mailing in self.browse(cr, uid, ids, context=context): - results[mass_mailing.id] = { - 'sent': len(mass_mailing.statistics_ids), - 'delivered': len([stat for stat in mass_mailing.statistics_ids if not stat.bounced]), # mail.state == 'sent' and - 'opened': len([stat for stat in mass_mailing.statistics_ids if stat.opened]), - 'replied': len([stat for stat in mass_mailing.statistics_ids if stat.replied]), - 'bounced': len([stat for stat in mass_mailing.statistics_ids if stat.bounced]), - } - return results - - _columns = { - 'name': fields.char('Name', required=True), - 'mass_mailing_campaign_id': fields.many2one( - 'mail.mass_mailing.campaign', 'Mass Mailing Campaign', - ondelete='cascade', required=True, - ), - 'template_id': fields.many2one( - 'email.template', 'Email Template', - ondelete='set null', - ), - 'domain': fields.char('Domain'), - 'date': fields.datetime('Date'), - 'color': fields.related( - 'mass_mailing_campaign_id', 'color', - type='integer', string='Color Index', - ), - # statistics data - 'statistics_ids': fields.one2many( - 'mail.mail.statistics', 'mass_mailing_id', - 'Emails Statistics', - ), - 'sent': fields.function( - _get_statistics, - string='Sent Emails', - type='integer', multi='_get_statistics' - ), - 'delivered': fields.function( - _get_statistics, - string='Delivered', - type='integer', multi='_get_statistics', - ), - 'opened': fields.function( - _get_statistics, - string='Opened', - type='integer', multi='_get_statistics', - ), - 'replied': fields.function( - _get_statistics, - string='Replied', - type='integer', multi='_get_statistics' - ), - 'bounced': fields.function( - _get_statistics, - string='Bounce', - type='integer', multi='_get_statistics' - ), - # monthly ratio - 'opened_monthly': fields.function( - _get_daily_statistics, - string='Opened', - type='char', multi='_get_daily_statistics', - ), - 'replied_monthly': fields.function( - _get_daily_statistics, - string='Replied', - type='char', multi='_get_daily_statistics', - ), - } - - _defaults = { - 'date': fields.datetime.now, - } - - -class MailMailStats(osv.Model): - """ MailMailStats models the statistics collected about emails. Those statistics - are stored in a separated model and table to avoid bloating the mail_mail table - with statistics values. This also allows to delete emails send with mass mailing - without loosing the statistics about them. """ - - _name = 'mail.mail.statistics' - _description = 'Email Statistics' - _rec_name = 'message_id' - _order = 'message_id' - - _columns = { - 'mail_mail_id': fields.integer( - 'Mail ID', - help='ID of the related mail_mail. This field is an integer field because' - 'the related mail_mail can be deleted separately from its statistics.' - ), - 'message_id': fields.char( - 'Message-ID', - ), - 'model': fields.char( - 'Document model', - ), - 'res_id': fields.integer( - 'Document ID', - ), - # campaign / wave data - 'mass_mailing_id': fields.many2one( - 'mail.mass_mailing', 'Mass Mailing', - ondelete='set null', - ), - 'mass_mailing_campaign_id': fields.related( - 'mass_mailing_id', 'mass_mailing_campaign_id', - type='many2one', ondelete='set null', - relation='mail.mass_mailing.campaign', - string='Mass Mailing Campaign', - store=True, readonly=True, - ), - 'template_id': fields.related( - 'mass_mailing_id', 'template_id', - type='many2one', ondelete='set null', - relation='email.template', - string='Email Template', - store=True, readonly=True, - ), - # Bounce and tracking - 'opened': fields.datetime( - 'Opened', - help='Date when this email has been opened for the first time.'), - 'replied': fields.datetime( - 'Replied', - help='Date when this email has been replied for the first time.'), - 'bounced': fields.datetime( - 'Bounced', - help='Date when this email has bounced.' - ), - } - - def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): - """ Set as opened """ - if not ids and mail_mail_ids: - ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context) - elif not ids and mail_message_ids: - ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context) - else: - ids = [] - for stat in self.browse(cr, uid, ids, context=context): - if not stat.opened: - self.write(cr, uid, [stat.id], {'opened': fields.datetime.now()}, context=context) - return ids - - def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): - """ Set as replied """ - if not ids and mail_mail_ids: - ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context) - elif not ids and mail_message_ids: - ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context) - else: - ids = [] - for stat in self.browse(cr, uid, ids, context=context): - if not stat.replied: - self.write(cr, uid, [stat.id], {'replied': fields.datetime.now()}, context=context) - return ids - - def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): - """ Set as bounced """ - if not ids and mail_mail_ids: - ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context) - elif not ids and mail_message_ids: - ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context) - else: - ids = [] - for stat in self.browse(cr, uid, ids, context=context): - if not stat.bounced: - self.write(cr, uid, [stat.id], {'bounced': fields.datetime.now()}, context=context) - return ids diff --git a/addons/mass_mailing/mass_mailing_demo.xml b/addons/mass_mailing/mass_mailing_demo.xml deleted file mode 100644 index 5a2388846e4..00000000000 --- a/addons/mass_mailing/mass_mailing_demo.xml +++ /dev/null @@ -1,82 +0,0 @@ -<?xml version="1.0"?> -<openerp> - <!-- <data noupdate="1"> --> - <data> - - <record id="mass_mail_template_1" model="email.template"> - <field name="name">Partner Newsletter 1</field> - <field name="model_id" ref="base.model_res_partner"/> - <field name="auto_delete" eval="False"/> - <field name="partner_to">${object.id}</field> - <field name="body_html"><![CDATA[<p>Hello</p>]]></field> - </record> - <record id="mass_mail_template_2" model="email.template"> - <field name="name">Partner Newsletter 2</field> - <field name="model_id" ref="base.model_res_partner"/> - <field name="auto_delete" eval="False"/> - <field name="partner_to">${object.id}</field> - <field name="body_html"><![CDATA[<p>Hello</p>]]></field> - </record> - - <record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign"> - <field name="name">Partners Newsletter</field> - <field name="user_id" eval="ref('base.user_root')"/> - </record> - - <record id="mass_mail_1" model="mail.mass_mailing"> - <field name="name">First Newsletter</field> - <field name="template_id" eval="ref('mass_mail_template_1')"/> - <field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/> - <field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/> - </record> - <record id="mass_mail_2" model="mail.mass_mailing"> - <field name="name">Second Newsletter</field> - <field name="template_id" eval="ref('mass_mail_template_2')"/> - <field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/> - <field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/> - </record> - - <record id="mass_mail_email_1" model="mail.mail.statistics"> - <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> - <field name="message_id">1111000@OpenERP.com</field> - <field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/> - <field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/> - </record> - <record id="mass_mail_email_2" model="mail.mail.statistics"> - <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> - <field name="message_id">1111001@OpenERP.com</field> - <field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/> - <field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/> - </record> - <record id="mass_mail_email_3" model="mail.mail.statistics"> - <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> - <field name="message_id">1111002@OpenERP.com</field> - <field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/> - </record> - <record id="mass_mail_email_4" model="mail.mail.statistics"> - <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> - <field name="message_id">1111003@OpenERP.com</field> - </record> - <record id="mass_mail_email_5" model="mail.mail.statistics"> - <field name="mass_mailing_id" eval="ref('mass_mail_1')"/> - <field name="message_id">1111004@OpenERP.com</field> - <field name="bounced" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/> - </record> - - <record id="mass_mail_email_2_1" model="mail.mail.statistics"> - <field name="mass_mailing_id" eval="ref('mass_mail_2')"/> - <field name="message_id">1111005@OpenERP.com</field> - <field name="opened" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/> - </record> - <record id="mass_mail_email_2_2" model="mail.mail.statistics"> - <field name="mass_mailing_id" eval="ref('mass_mail_2')"/> - <field name="message_id">1111006@OpenERP.com</field> - <field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/> - </record> - <record id="mass_mail_email_2_3" model="mail.mail.statistics"> - <field name="mass_mailing_id" eval="ref('mass_mail_2')"/> - <field name="message_id">1111007@OpenERP.com</field> - </record> - - </data> -</openerp> diff --git a/addons/mass_mailing/mass_mailing_view.xml b/addons/mass_mailing/mass_mailing_view.xml deleted file mode 100644 index e8848fba44f..00000000000 --- a/addons/mass_mailing/mass_mailing_view.xml +++ /dev/null @@ -1,379 +0,0 @@ -<?xml version="1.0"?> -<openerp> - <data> - - <!-- MASS MAILING !--> - <record model="ir.ui.view" id="view_mail_mass_mailing_search"> - <field name="name">mail.mass_mailing.search</field> - <field name="model">mail.mass_mailing</field> - <field name="arch" type="xml"> - <search string="Mass Mailings"> - <field name="name" string="Mailings"/> - <field name="mass_mailing_campaign_id"/> - <field name="template_id"/> - <group expand="0" string="Group By..."> - <filter string="Campaign" name="group_mass_mailing_campaign_id" - context="{'group_by': 'mass_mailing_campaign_id'}"/> - <filter string="Template" name="group_template_id" - context="{'group_by': 'template_id'}"/> - </group> - </search> - </field> - </record> - - <record model="ir.ui.view" id="view_mail_mass_mailing_tree"> - <field name="name">mail.mass_mailing.tree</field> - <field name="model">mail.mass_mailing</field> - <field name="priority">10</field> - <field name="arch" type="xml"> - <tree string="Mass Mailings"> - <field name="name"/> - <field name="sent"/> - <field name="delivered"/> - <field name="opened"/> - <field name="replied"/> - <field name="mass_mailing_campaign_id" invisible="1"/> - <field name="template_id" invisible="1"/> - </tree> - </field> - </record> - - <record model="ir.ui.view" id="view_mail_mass_mailing_form"> - <field name="name">mail.mass_mailing.form</field> - <field name="model">mail.mass_mailing</field> - <field name="arch" type="xml"> - <form string="Mass Mailing" version="7.0"> - <sheet> - <group> - <group> - <field name="name"/> - <field name="mass_mailing_campaign_id" readonly="True"/> - </group> - <group> - <field name="template_id"/> - <field name="domain"/> - <field name="date"/> - </group> - </group> - <group string="Email Statistics"> - <field name="statistics_ids" nolabel="1" colspan="2"/> - <group> - <field name="sent"/> - <field name="opened"/> - <field name="bounced"/> - </group> - <group> - <field name="delivered"/> - <field name="replied"/> - </group> - </group> - </sheet> - </form> - </field> - </record> - - <record model="ir.ui.view" id="view_mail_mass_mailing_kanban"> - <field name="name">mail.mass_mailing.kanban</field> - <field name="model">mail.mass_mailing</field> - <field name="arch" type="xml"> - <kanban> - <field name='color'/> - <templates> - <t t-name="kanban-box"> - <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing oe_kanban_mass_mailing_segment"> - <div class="oe_kanban_content"> - <div> - <h3> - <field name="name"/> - </h3> - <p style="margin-left: 10px; margin-top: 8px;"> - Sent: <field name="date"/><br /> - Campaign: <field name="mass_mailing_campaign_id"/> - </p> - </div> - <div> - <p class="oe_mail_stats"> - <span class="oe_mail_result"><field name="sent"/></span><br /> - Sent - </p> - <p class="oe_mail_stats"> - <span class="oe_mail_result"><field name="delivered"/></span><br /> - Delivered - </p> - <p class="oe_mail_stats"> - <span class="oe_mail_result"><field name="opened"/></span><br /> - Opened - </p> - <p class="oe_mail_stats"> - <span class="oe_mail_result"><field name="replied"/></span><br /> - Replied - </p> - </div> - <div> - <div class="oe_sparkline_container"> - <h4 class="oe_sparkline_bar_title">Opened</h4><br /> - <field name="opened_monthly" widget="sparkline_bar" options="{'height': '50px', 'barWidth': 10, 'barSpacing': 5}"/> - </div> - <div class="oe_sparkline_container"> - <h4 class="oe_sparkline_bar_title">Replied</h4><br /> - <field name="replied_monthly" widget="sparkline_bar" options="{'height': '50px', 'barWidth': 10, 'barSpacing': 5}"/> - </div> - </div> - </div> - <div class="oe_clear"></div> - </div> - </t> - </templates> - </kanban> - </field> - </record> - - <record id="action_view_mass_mailings" model="ir.actions.act_window"> - <field name="name">Mass Mailings</field> - <field name="res_model">mail.mass_mailing</field> - <field name="view_type">form</field> - <field name="view_mode">kanban,tree,form</field> - </record> - - <record id="action_view_mass_mailings_from_campaign" model="ir.actions.act_window"> - <field name="name">Mass Mailings</field> - <field name="res_model">mail.mass_mailing</field> - <field name="view_type">form</field> - <field name="view_mode">kanban,tree,form</field> - <field name="context">{ - 'search_default_mass_mailing_campaign_id': [active_id], - 'default_mass_mailing_campaign_id': active_id, - } - </field> - </record> - - <!-- MASS MAILING CAMPAIGNS !--> - <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_search"> - <field name="name">mail.mass_mailing.campaign.search</field> - <field name="model">mail.mass_mailing.campaign</field> - <field name="arch" type="xml"> - <search string="Mass Mailing Campaigns"> - <field name="name" string="Campaigns"/> - <field name="user_id"/> - <group expand="0" string="Group By..."> - <filter string="Responsibles" name="group_user_id" - context="{'group_by': 'user_id'}"/> - </group> - </search> - </field> - </record> - - <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_tree"> - <field name="name">mail.mass_mailing.campaign.tree</field> - <field name="model">mail.mass_mailing.campaign</field> - <field name="priority">10</field> - <field name="arch" type="xml"> - <tree string="Mass Mailing Campaigns"> - <field name="name"/> - <field name="user_id"/> - </tree> - </field> - </record> - - <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form"> - <field name="name">mail.mass_mailing.campaign.form</field> - <field name="model">mail.mass_mailing.campaign</field> - <field name="arch" type="xml"> - <form string="Mass Mailing Campaign" version="7.0"> - <header> - <button name="launch_mass_mailing_create_wizard" type="object" - class="oe_highlight" string="Create a New Mailing"/> - </header> - <sheet> - <group> - <field name="name"/> - <field name="user_id"/> - </group> - <group> - <group> - <field name="sent"/> - <field name="opened"/> - <field name="bounced"/> - </group> - <group> - <field name="delivered"/> - <field name="replied"/> - </group> - </group> - <group> - <field name="mass_mailing_ids" readonly="1"/> - </group> - </sheet> - </form> - </field> - </record> - - <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_kanban"> - <field name="name">mail.mass_mailing.campaign.kanban</field> - <field name="model">mail.mass_mailing.campaign</field> - <field name="arch" type="xml"> - <kanban> - <field name="mass_mailing_kanban_ids"/> - <field name='sent'/> - <field name='color'/> - <templates> - <t t-name="mass_mailing.mass_mailing"> - <div class="oe_mass_mailings"> - <div> - <a name="%(action_view_mass_mailings_from_campaign)d" type="action"> - <h4><t t-raw="mass_mailing.name"/></h4> - </a> - </div> - <div> - <p class="oe_mail_stats"> - <span class="oe_mail_result"><t t-raw="mass_mailing.sent"/></span><br /> - Sent - </p> - <p class="oe_mail_stats"> - <span class="oe_mail_result"><t t-raw="mass_mailing.delivered"/></span><br /> - Delivered - </p> - <p class="oe_mail_stats"> - <span class="oe_mail_result"><t t-raw="mass_mailing.opened"/></span><br /> - Opened - </p> - <p class="oe_mail_stats"> - <span class="oe_mail_result"><t t-raw="mass_mailing.replied"/></span><br /> - Replied - </p> - </div> - </div> - </t> - <t t-name="kanban-box"> - <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing oe_kanban_mass_mailing_campaign"> - <div class="oe_dropdown_toggle oe_dropdown_kanban"> - <span class="oe_e">i</span> - <ul class="oe_dropdown_menu"> - <t t-if="widget.view.is_action_enabled('edit')"> - <li><a type="edit">Settings</a></li> - </t> - <t t-if="widget.view.is_action_enabled('edit')"> - <li><a name="%(action_mail_mass_mailing_create)d" type="action">New Wave</a></li> - </t> - <t t-if="widget.view.is_action_enabled('delete')"> - <li><a type="delete">Delete</a></li> - </t> - <li><ul class="oe_kanban_colorpicker" data-field="color"/></li> - </ul> - </div> - <div class="oe_kanban_content"> - <h3> - <field name="name"/> - </h3> - <div> - <field name="delivered" widget="gauge" style="width:160px; height: 120px;" - options="{'max_field': 'sent'}"/> - <field name="opened" widget="gauge" style="width:160px; height: 120px;" - options="{'max_field': 'sent'}"/> - <field name="replied" widget="gauge" style="width:160px; height: 120px;" - options="{'max_field': 'sent'}"/> - </div> - <t t-foreach='record.mass_mailing_kanban_ids.value' t-as='mass_mailing'> - <t t-call="mass_mailing.mass_mailing"/> - </t> - </div> - <div class="oe_clear"></div> - </div> - </t> - </templates> - </kanban> - </field> - </record> - - <record id="action_view_mass_mailing_campaigns" model="ir.actions.act_window"> - <field name="name">Mass Mailing Campaigns</field> - <field name="res_model">mail.mass_mailing.campaign</field> - <field name="view_type">form</field> - <field name="view_mode">kanban,tree,form</field> - <field name="help" type="html"> - <p class="oe_view_nocontent_create"> - Click to define a new mass mailing campaign. - </p><p> - Create a campaign to structure mass mailing and get analysis from email status. - </p> - </field> - </record> - - <!-- MAIL MAIL STATISTICS !--> - <record model="ir.ui.view" id="view_mail_mail_statistics_search"> - <field name="name">mail.mail.statistics.search</field> - <field name="model">mail.mail.statistics</field> - <field name="arch" type="xml"> - <search string="Mail Statistics"> - <field name="mail_mail_id"/> - <field name="message_id"/> - </search> - </field> - </record> - - <record model="ir.ui.view" id="view_mail_mail_statistics_tree"> - <field name="name">mail.mail.statistics.tree</field> - <field name="model">mail.mail.statistics</field> - <field name="arch" type="xml"> - <tree string="Mail Statistics"> - <field name="mail_mail_id"/> - <field name="message_id"/> - <field name="opened"/> - <field name="replied"/> - <field name="bounced"/> - </tree> - </field> - </record> - - <record model="ir.ui.view" id="view_mail_mail_statistics_form"> - <field name="name">mail.mail.statistics.form</field> - <field name="model">mail.mail.statistics</field> - <field name="arch" type="xml"> - <form string="Mail Statistics" version="7.0"> - <group> - <group> - <field name="mail_mail_id"/> - <field name="message_id"/> - <field name="opened"/> - <field name="replied"/> - <field name="bounced"/> - </group> - <group> - <field name="mass_mailing_id"/> - <field name="mass_mailing_campaign_id"/> - <field name="template_id"/> - <field name="model"/> - <field name="res_id"/> - </group> - </group> - </form> - </field> - </record> - - <record id="action_view_mail_mail_statistics" model="ir.actions.act_window"> - <field name="name">Mail Statistics</field> - <field name="res_model">mail.mail.statistics</field> - <field name="view_type">form</field> - <field name="view_mode">tree,form</field> - </record> - - <!-- Top menu item --> - <menuitem name="Marketing" id="base.marketing_menu" sequence="85" groups="base.group_user"/> - - <!-- Add in marketing --> - <menuitem name="Mass Mailing" id="mass_mailing_campaign" - parent="base.marketing_menu" sequence="1"/> - <menuitem name="Campaigns" id="menu_email_campaigns" - parent="mass_mailing_campaign" sequence="1" - action="action_view_mass_mailing_campaigns"/> - <menuitem name="Mass Mailings" id="menu_email_mass_mailings" - parent="mass_mailing_campaign" sequence="2" - action="action_view_mass_mailings"/> - - <!-- Add in Technical/Email --> - <menuitem name="Mail Statistics" id="menu_email_statistics" - parent="base.menu_email" sequence="50" - action="action_view_mail_mail_statistics"/> - - </data> -</openerp> diff --git a/addons/mass_mailing/models/__init__.py b/addons/mass_mailing/models/__init__.py new file mode 100644 index 00000000000..2cffe48642b --- /dev/null +++ b/addons/mass_mailing/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +import mass_mailing +import mass_mailing_stats +import mail_mail +import mail_thread +import res_config diff --git a/addons/mass_mailing/mail_mail.py b/addons/mass_mailing/models/mail_mail.py index 917334bdda4..a67f88a2a28 100644 --- a/addons/mass_mailing/mail_mail.py +++ b/addons/mass_mailing/models/mail_mail.py @@ -19,7 +19,8 @@ # ############################################################################## -from urlparse import urljoin +import urllib +import urlparse from openerp import tools from openerp import SUPERUSER_ID @@ -32,6 +33,7 @@ class MailMail(osv.Model): _inherit = ['mail.mail'] _columns = { + 'mailing_id': fields.many2one('mail.mass_mailing', 'Mass Mailing'), 'statistics_ids': fields.one2many( 'mail.mail.statistics', 'mail_mail_id', string='Statistics', @@ -50,9 +52,24 @@ class MailMail(osv.Model): def _get_tracking_url(self, cr, uid, mail, partner=None, context=None): base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') - track_url = urljoin(base_url, 'mail/track/%d/blank.gif' % mail.id) + track_url = urlparse.urljoin( + base_url, 'mail/track/%(mail_id)s/blank.gif?%(params)s' % { + 'mail_id': mail.id, + 'params': urllib.urlencode({'db': cr.dbname}) + } + ) return '<img src="%s" alt=""/>' % track_url + def _get_unsubscribe_url(self, cr, uid, mail, email_to, msg=None, context=None): + base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') + url = urlparse.urljoin( + base_url, 'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % { + 'mailing_id': mail.mailing_id.id, + 'params': urllib.urlencode({'db': cr.dbname, 'res_id': mail.res_id, 'email': email_to}) + } + ) + return '<small><a href="%s">%s</a></small>' % (url, msg or 'Click to unsubscribe') + def send_get_mail_body(self, cr, uid, mail, partner=None, context=None): """ Override to add the tracking URL to the body. """ body = super(MailMail, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context) @@ -63,3 +80,19 @@ class MailMail(osv.Model): if tracking_url: body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div') return body + + def send_get_email_dict(self, cr, uid, mail, partner=None, context=None): + res = super(MailMail, self).send_get_email_dict(cr, uid, mail, partner, context=context) + if mail.mailing_id and res.get('body') and res.get('email_to'): + email_to = tools.email_split(res.get('email_to')[0]) + unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context) + if unsubscribe_url: + res['body'] = tools.append_content_to_html(res['body'], unsubscribe_url, plaintext=False, container_tag='p') + return res + + def _postprocess_sent_message(self, cr, uid, mail, context=None, mail_sent=True): + if mail_sent is True and mail.statistics_ids: + self.pool['mail.mail.statistics'].write(cr, uid, [s.id for s in mail.statistics_ids], {'sent': fields.datetime.now()}, context=context) + elif mail_sent is False and mail.statistics_ids: + self.pool['mail.mail.statistics'].write(cr, uid, [s.id for s in mail.statistics_ids], {'exception': fields.datetime.now()}, context=context) + return super(MailMail, self)._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent) diff --git a/addons/mass_mailing/mail_thread.py b/addons/mass_mailing/models/mail_thread.py index 524341f88d7..524341f88d7 100644 --- a/addons/mass_mailing/mail_thread.py +++ b/addons/mass_mailing/models/mail_thread.py diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py new file mode 100644 index 00000000000..bd314f03a3d --- /dev/null +++ b/addons/mass_mailing/models/mass_mailing.py @@ -0,0 +1,571 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime +from dateutil import relativedelta +import json +import random +import urllib +import urlparse + +from openerp import tools +from openerp.tools.safe_eval import safe_eval as eval +from openerp.tools.translate import _ +from openerp.osv import osv, fields + + +class MassMailingCategory(osv.Model): + """Model of categories of mass mailing, i.e. marketing, newsletter, ... """ + _name = 'mail.mass_mailing.category' + _description = 'Mass Mailing Category' + _order = 'name' + + _columns = { + 'name': fields.char('Name', required=True), + } + + +class MassMailingContact(osv.Model): + """Model of a contact. This model is different from the partner model + because it holds only some basic information: name, email. The purpose is to + be able to deal with large contact list to email without bloating the partner + base.""" + _name = 'mail.mass_mailing.contact' + _description = 'Mass Mailing Contact' + _order = 'email' + _rec_name = 'email' + + _columns = { + 'name': fields.char('Name'), + 'email': fields.char('Email', required=True), + 'create_date': fields.datetime('Create Date'), + 'list_id': fields.many2one( + 'mail.mass_mailing.list', string='Mailing List', + ondelete='cascade', required=True, + ), + 'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive mails anymore from this list'), + } + + def _get_latest_list(self, cr, uid, context={}): + lid = self.pool.get('mail.mass_mailing.list').search(cr, uid, [], limit=1, order='id desc', context=context) + return lid and lid[0] or False + + _defaults = { + 'list_id': _get_latest_list + } + + def name_create(self, cr, uid, name, context=None): + name, email = self.pool['res.partner']._parse_partner_name(name, context=context) + if name and not email: + email = name + if email and not name: + name = email + rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context) + return self.name_get(cr, uid, [rec_id], context)[0] + + +class MassMailingList(osv.Model): + """Model of a contact list. """ + _name = 'mail.mass_mailing.list' + _order = 'name' + _description = 'Mailing List' + + def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None): + result = dict.fromkeys(ids, 0) + Contacts = self.pool.get('mail.mass_mailing.contact') + for group in Contacts.read_group(cr, uid, [('list_id', 'in', ids), ('opt_out', '!=', True)], ['list_id'], ['list_id'], context=context): + result[group['list_id'][0]] = group['list_id_count'] + return result + + _columns = { + 'name': fields.char('Mailing List', required=True), + 'contact_nbr': fields.function( + _get_contact_nbr, type='integer', + string='Number of Contacts', + ), + } + + +class MassMailingStage(osv.Model): + """Stage for mass mailing campaigns. """ + _name = 'mail.mass_mailing.stage' + _description = 'Mass Mailing Campaign Stage' + _order = 'sequence' + + _columns = { + 'name': fields.char('Name', required=True, translate=True), + 'sequence': fields.integer('Sequence'), + } + + _defaults = { + 'sequence': 0, + } + + +class MassMailingCampaign(osv.Model): + """Model of mass mailing campaigns. """ + _name = "mail.mass_mailing.campaign" + _description = 'Mass Mailing Campaign' + + def _get_statistics(self, cr, uid, ids, name, arg, context=None): + """ Compute statistics of the mass mailing campaign """ + Statistics = self.pool['mail.mail.statistics'] + results = dict.fromkeys(ids, False) + for cid in ids: + stat_ids = Statistics.search(cr, uid, [('mass_mailing_campaign_id', '=', cid)], context=context) + stats = Statistics.browse(cr, uid, stat_ids, context=context) + results[cid] = { + 'total': len(stats), + 'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]), + 'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]), + 'sent': len([s for s in stats if not s.sent is False]), + 'opened': len([s for s in stats if not s.opened is False]), + 'replied': len([s for s in stats if not s.replied is False]), + 'bounced': len([s for s in stats if not s.bounced is False]), + } + results[cid]['delivered'] = results[cid]['sent'] - results[cid]['bounced'] + results[cid]['received_ratio'] = 100.0 * results[cid]['delivered'] / (results[cid]['total'] or 1) + results[cid]['opened_ratio'] = 100.0 * results[cid]['opened'] / (results[cid]['total'] or 1) + results[cid]['replied_ratio'] = 100.0 * results[cid]['replied'] / (results[cid]['total'] or 1) + return results + + _columns = { + 'name': fields.char('Name', required=True), + 'stage_id': fields.many2one('mail.mass_mailing.stage', 'Stage', required=True), + 'user_id': fields.many2one( + 'res.users', 'Responsible', + required=True, + ), + 'category_ids': fields.many2many( + 'mail.mass_mailing.category', 'mail_mass_mailing_category_rel', + 'category_id', 'campaign_id', string='Categories'), + 'mass_mailing_ids': fields.one2many( + 'mail.mass_mailing', 'mass_mailing_campaign_id', + 'Mass Mailings', + ), + 'unique_ab_testing': fields.boolean( + 'AB Testing', + help='If checked, recipients will be mailed only once, allowing to send' + 'various mailings in a single campaign to test the effectiveness' + 'of the mailings.'), + 'color': fields.integer('Color Index'), + # stat fields + 'total': fields.function( + _get_statistics, string='Total', + type='integer', multi='_get_statistics' + ), + 'scheduled': fields.function( + _get_statistics, string='Scheduled', + type='integer', multi='_get_statistics' + ), + 'failed': fields.function( + _get_statistics, string='Failed', + type='integer', multi='_get_statistics' + ), + 'sent': fields.function( + _get_statistics, string='Sent Emails', + type='integer', multi='_get_statistics' + ), + 'delivered': fields.function( + _get_statistics, string='Delivered', + type='integer', multi='_get_statistics', + ), + 'opened': fields.function( + _get_statistics, string='Opened', + type='integer', multi='_get_statistics', + ), + 'replied': fields.function( + _get_statistics, string='Replied', + type='integer', multi='_get_statistics' + ), + 'bounced': fields.function( + _get_statistics, string='Bounced', + type='integer', multi='_get_statistics' + ), + 'received_ratio': fields.function( + _get_statistics, string='Received Ratio', + type='integer', multi='_get_statistics', + ), + 'opened_ratio': fields.function( + _get_statistics, string='Opened Ratio', + type='integer', multi='_get_statistics', + ), + 'replied_ratio': fields.function( + _get_statistics, string='Replied Ratio', + type='integer', multi='_get_statistics', + ), + } + + def _get_default_stage_id(self, cr, uid, context=None): + stage_ids = self.pool['mail.mass_mailing.stage'].search(cr, uid, [], limit=1, context=context) + return stage_ids and stage_ids[0] or False + + _defaults = { + 'user_id': lambda self, cr, uid, ctx=None: uid, + 'stage_id': lambda self, *args: self._get_default_stage_id(*args), + } + + def get_recipients(self, cr, uid, ids, model=None, context=None): + """Return the recipients of a mailing campaign. This is based on the statistics + build for each mailing. """ + Statistics = self.pool['mail.mail.statistics'] + res = dict.fromkeys(ids, False) + for cid in ids: + domain = [('mass_mailing_campaign_id', '=', cid)] + if model: + domain += [('model', '=', model)] + stat_ids = Statistics.search(cr, uid, domain, context=context) + res[cid] = set(stat.res_id for stat in Statistics.browse(cr, uid, stat_ids, context=context)) + return res + + +class MassMailing(osv.Model): + """ MassMailing models a wave of emails for a mass mailign campaign. + A mass mailing is an occurence of sending emails. """ + + _name = 'mail.mass_mailing' + _description = 'Mass Mailing' + # number of periods for tracking mail_mail statistics + _period_number = 6 + _order = 'sent_date DESC' + + def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, date_begin, context=None): + """ Generic method to generate data for bar chart values using SparklineBarWidget. + This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). + + :param obj: the target model (i.e. crm_lead) + :param domain: the domain applied to the read_group + :param list read_fields: the list of fields to read in the read_group + :param str value_field: the field used to compute the value of the bar slice + :param str groupby_field: the fields used to group + + :return list section_result: a list of dicts: [ + { 'value': (int) bar_column_value, + 'tootip': (str) bar_column_tooltip, + } + ] + """ + date_begin = date_begin.date() + section_result = [{'value': 0, + 'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'), + } for i in range(0, self._period_number)] + group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) + field_col_info = obj._all_columns.get(groupby_field.split(':')[0]) + pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT + for group in group_obj: + group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date() + timedelta = relativedelta.relativedelta(group_begin_date, date_begin) + section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)} + return section_result + + def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None): + """ Get the daily statistics of the mass mailing. This is done by a grouping + on opened and replied fields. Using custom format in context, we obtain + results for the next 6 days following the mass mailing date. """ + obj = self.pool['mail.mail.statistics'] + res = {} + for mailing in self.browse(cr, uid, ids, context=context): + res[mailing.id] = {} + date = mailing.sent_date if mailing.sent_date else mailing.create_date + date_begin = datetime.strptime(date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1) + date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) + date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) + domain = [('mass_mailing_id', '=', mailing.id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)] + res[mailing.id]['opened_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened:day', date_begin, context=context)) + domain = [('mass_mailing_id', '=', mailing.id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)] + res[mailing.id]['replied_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied:day', date_begin, context=context)) + return res + + def _get_statistics(self, cr, uid, ids, name, arg, context=None): + """ Compute statistics of the mass mailing campaign """ + Statistics = self.pool['mail.mail.statistics'] + results = dict.fromkeys(ids, False) + for mid in ids: + stat_ids = Statistics.search(cr, uid, [('mass_mailing_id', '=', mid)], context=context) + stats = Statistics.browse(cr, uid, stat_ids, context=context) + results[mid] = { + 'total': len(stats), + 'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]), + 'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]), + 'sent': len([s for s in stats if not s.sent is False]), + 'opened': len([s for s in stats if not s.opened is False]), + 'replied': len([s for s in stats if not s.replied is False]), + 'bounced': len([s for s in stats if not s.bounced is False]), + } + results[mid]['delivered'] = results[mid]['sent'] - results[mid]['bounced'] + results[mid]['received_ratio'] = 100.0 * results[mid]['delivered'] / (results[mid]['total'] or 1) + results[mid]['opened_ratio'] = 100.0 * results[mid]['opened'] / (results[mid]['total'] or 1) + results[mid]['replied_ratio'] = 100.0 * results[mid]['replied'] / (results[mid]['total'] or 1) + return results + + def _get_mailing_model(self, cr, uid, context=None): + res = [] + for model_name in self.pool: + model = self.pool[model_name] + if hasattr(model, '_mail_mass_mailing') and getattr(model, '_mail_mass_mailing'): + res.append((model._name, getattr(model, '_mail_mass_mailing'))) + res.append(('mail.mass_mailing.contact', _('Mailing List'))) + return res + + # indirections for inheritance + _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(*args, **kwargs) + + _columns = { + 'name': fields.char('Subject', required=True), + 'email_from': fields.char('From', required=True), + 'create_date': fields.datetime('Creation Date'), + 'sent_date': fields.datetime('Sent Date'), + 'body_html': fields.html('Body'), + 'attachment_ids': fields.many2many( + 'ir.attachment', 'mass_mailing_ir_attachments_rel', + 'mass_mailing_id', 'attachment_id', 'Attachments' + ), + 'mass_mailing_campaign_id': fields.many2one( + 'mail.mass_mailing.campaign', 'Mass Mailing Campaign', + ondelete='set null', + ), + 'state': fields.selection( + [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')], + string='Status', required=True, + ), + 'color': fields.related( + 'mass_mailing_campaign_id', 'color', + type='integer', string='Color Index', + ), + # mailing options + 'reply_to_mode': fields.selection( + [('thread', 'In Document'), ('email', 'Specified Email Address')], + string='Reply-To Mode', required=True, + ), + 'reply_to': fields.char('Reply To', help='Preferred Reply-To Address'), + # recipients + 'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True), + 'mailing_domain': fields.char('Domain'), + 'contact_list_ids': fields.many2many( + 'mail.mass_mailing.list', 'mail_mass_mailing_list_rel', + string='Mailing Lists', + ), + 'contact_ab_pc': fields.integer( + 'AB Testing percentage', + help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.' + ), + # statistics data + 'statistics_ids': fields.one2many( + 'mail.mail.statistics', 'mass_mailing_id', + 'Emails Statistics', + ), + 'total': fields.function( + _get_statistics, string='Total', + type='integer', multi='_get_statistics', + ), + 'scheduled': fields.function( + _get_statistics, string='Scheduled', + type='integer', multi='_get_statistics', + ), + 'failed': fields.function( + _get_statistics, string='Failed', + type='integer', multi='_get_statistics', + ), + 'sent': fields.function( + _get_statistics, string='Sent', + type='integer', multi='_get_statistics', + ), + 'delivered': fields.function( + _get_statistics, string='Delivered', + type='integer', multi='_get_statistics', + ), + 'opened': fields.function( + _get_statistics, string='Opened', + type='integer', multi='_get_statistics', + ), + 'replied': fields.function( + _get_statistics, string='Replied', + type='integer', multi='_get_statistics', + ), + 'bounced': fields.function( + _get_statistics, string='Bounced', + type='integer', multi='_get_statistics', + ), + 'received_ratio': fields.function( + _get_statistics, string='Received Ratio', + type='integer', multi='_get_statistics', + ), + 'opened_ratio': fields.function( + _get_statistics, string='Opened Ratio', + type='integer', multi='_get_statistics', + ), + 'replied_ratio': fields.function( + _get_statistics, string='Replied Ratio', + type='integer', multi='_get_statistics', + ), + # dayly ratio + 'opened_dayly': fields.function( + _get_daily_statistics, string='Opened', + type='char', multi='_get_daily_statistics', + oldname='opened_monthly', + ), + 'replied_dayly': fields.function( + _get_daily_statistics, string='Replied', + type='char', multi='_get_daily_statistics', + oldname='replied_monthly', + ) + } + + def default_get(self, cr, uid, fields, context=None): + res = super(MassMailing, self).default_get(cr, uid, fields, context=context) + if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get('mailing_model'): + if res['mailing_model'] in ['res.partner', 'mail.mass_mailing.contact']: + res['reply_to_mode'] = 'email' + else: + res['reply_to_mode'] = 'thread' + return res + + _defaults = { + 'state': 'draft', + 'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx), + 'reply_to': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx), + 'mailing_model': 'mail.mass_mailing.contact', + 'contact_ab_pc': 100, + } + + #------------------------------------------------------ + # Technical stuff + #------------------------------------------------------ + + def copy_data(self, cr, uid, id, default=None, context=None): + if default is None: + default = {} + mailing = self.browse(cr, uid, id, context=context) + default.update({ + 'state': 'draft', + 'statistics_ids': [], + 'name': _('%s (duplicate)') % mailing.name, + 'sent_date': False, + }) + return super(MassMailing, self).copy_data(cr, uid, id, default, context=context) + + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True): + """ Override read_group to always display all states. """ + if groupby and groupby[0] == "state": + # Default result structure + # states = self._get_state_list(cr, uid, context=context) + states = [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')] + read_group_all_states = [{ + '__context': {'group_by': groupby[1:]}, + '__domain': domain + [('state', '=', state_value)], + 'state': state_value, + 'state_count': 0, + } for state_value, state_name in states] + # Get standard results + read_group_res = super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) + # Update standard results with default results + result = [] + for state_value, state_name in states: + res = filter(lambda x: x['state'] == state_value, read_group_res) + if not res: + res = filter(lambda x: x['state'] == state_value, read_group_all_states) + res[0]['state'] = [state_value, state_name] + result.append(res[0]) + return result + else: + return super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) + + #------------------------------------------------------ + # Views & Actions + #------------------------------------------------------ + + def on_change_model_and_list(self, cr, uid, ids, mailing_model, list_ids, context=None): + value = {} + if mailing_model == 'mail.mass_mailing.contact': + list_ids = map(lambda item: item if isinstance(item, (int, long)) else [lid for lid in item[2]], list_ids) + if list_ids: + value['mailing_domain'] = "[('list_id', 'in', %s)]" % list_ids + else: + value['mailing_domain'] = "[('list_id', '=', False)]" + else: + value['mailing_domain'] = False + return {'value': value} + + def action_duplicate(self, cr, uid, ids, context=None): + copy_id = None + for mid in ids: + copy_id = self.copy(cr, uid, mid, context=context) + if copy_id: + return { + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'mail.mass_mailing', + 'res_id': copy_id, + 'context': context, + } + return False + + def action_test_mailing(self, cr, uid, ids, context=None): + ctx = dict(context, default_mass_mailing_id=ids[0]) + return { + 'name': _('Test Mailing'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'mail.mass_mailing.test', + 'target': 'new', + 'context': ctx, + } + + def action_edit_html(self, cr, uid, ids, context=None): + if not len(ids) == 1: + raise ValueError('One and only one ID allowed for this action') + mail = self.browse(cr, uid, ids[0], context=context) + url = '/website_mail/email_designer?model=mail.mass_mailing&res_id=%d&template_model=%s&enable_editor=1' % (ids[0], mail.mailing_model) + return { + 'name': _('Open with Visual Editor'), + 'type': 'ir.actions.act_url', + 'url': url, + 'target': 'self', + } + + #------------------------------------------------------ + # Email Sending + #------------------------------------------------------ + + def get_recipients(self, cr, uid, mailing, context=None): + domain = eval(mailing.mailing_domain) + res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context) + + # randomly choose a fragment + if mailing.contact_ab_pc < 100: + contact_nbr = self.pool[mailing.mailing_model].search(cr, uid, domain, count=True, context=context) + topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc) + if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing: + already_mailed = self.pool['mail.mass_mailing.campaign'].get_recipients(cr, uid, [mailing.mass_mailing_campaign_id.id], context=context)[mailing.mass_mailing_campaign_id.id] + else: + already_mailed = set([]) + remaining = set(res_ids).difference(already_mailed) + if topick > len(remaining): + topick = len(remaining) + res_ids = random.sample(remaining, topick) + return res_ids + + def send_mail(self, cr, uid, ids, context=None): + author_id = self.pool['res.users'].browse(cr, uid, uid, context=context).partner_id.id + for mailing in self.browse(cr, uid, ids, context=context): + # instantiate an email composer + send emails + res_ids = self.get_recipients(cr, uid, mailing, context=context) + comp_ctx = dict(context, active_ids=res_ids) + composer_values = { + 'author_id': author_id, + 'body': mailing.body_html, + 'subject': mailing.name, + 'model': mailing.mailing_model, + 'email_from': mailing.email_from, + 'record_name': False, + 'composition_mode': 'mass_mail', + 'mass_mailing_id': mailing.id, + 'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids], + } + if mailing.reply_to_mode == 'email': + composer_values['reply_to'] = mailing.reply_to + composer_id = self.pool['mail.compose.message'].create(cr, uid, composer_values, context=comp_ctx) + self.pool['mail.compose.message'].send_mail(cr, uid, [composer_id], context=comp_ctx) + self.write(cr, uid, [mailing.id], {'sent_date': fields.datetime.now(), 'state': 'done'}, context=context) + return True diff --git a/addons/mass_mailing/models/mass_mailing_stats.py b/addons/mass_mailing/models/mass_mailing_stats.py new file mode 100644 index 00000000000..319a6fde14d --- /dev/null +++ b/addons/mass_mailing/models/mass_mailing_stats.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +############################################################################## + +from datetime import datetime +from dateutil import relativedelta +import random +try: + import simplejson as json +except ImportError: + import json +import urllib +import urlparse + +from openerp import tools +from openerp.exceptions import Warning +from openerp.tools.safe_eval import safe_eval as eval +from openerp.tools.translate import _ +from openerp.osv import osv, fields + + +class MailMailStats(osv.Model): + """ MailMailStats models the statistics collected about emails. Those statistics + are stored in a separated model and table to avoid bloating the mail_mail table + with statistics values. This also allows to delete emails send with mass mailing + without loosing the statistics about them. """ + + _name = 'mail.mail.statistics' + _description = 'Email Statistics' + _rec_name = 'message_id' + _order = 'message_id' + + _columns = { + 'mail_mail_id': fields.integer( + 'Mail ID', + help='ID of the related mail_mail. This field is an integer field because' + 'the related mail_mail can be deleted separately from its statistics.' + ), + 'message_id': fields.char('Message-ID'), + 'model': fields.char('Document model'), + 'res_id': fields.integer('Document ID'), + # campaign / wave data + 'mass_mailing_id': fields.many2one( + 'mail.mass_mailing', 'Mass Mailing', + ondelete='set null', + ), + 'mass_mailing_campaign_id': fields.related( + 'mass_mailing_id', 'mass_mailing_campaign_id', + type='many2one', ondelete='set null', + relation='mail.mass_mailing.campaign', + string='Mass Mailing Campaign', + store=True, readonly=True, + ), + # Bounce and tracking + 'scheduled': fields.datetime('Scheduled', help='Date when the email has been created'), + 'sent': fields.datetime('Sent', help='Date when the email has been sent'), + 'exception': fields.datetime('Exception', help='Date of technical error leading to the email not being sent'), + 'opened': fields.datetime('Opened', help='Date when the email has been opened the first time'), + 'replied': fields.datetime('Replied', help='Date when this email has been replied for the first time.'), + 'bounced': fields.datetime('Bounced', help='Date when this email has bounced.'), + } + + _defaults = { + 'scheduled': fields.datetime.now, + } + + def _get_ids(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, domain=None, context=None): + if not ids and mail_mail_ids: + base_domain = [('mail_mail_id', 'in', mail_mail_ids)] + elif not ids and mail_message_ids: + base_domain = [('message_id', 'in', mail_message_ids)] + else: + base_domain = [('id', 'in', ids or [])] + if domain: + base_domain = ['&'] + domain + base_domain + return self.search(cr, uid, base_domain, context=context) + + def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): + stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('opened', '=', False)], context) + self.write(cr, uid, stat_ids, {'opened': fields.datetime.now()}, context=context) + return stat_ids + + def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): + stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('replied', '=', False)], context) + self.write(cr, uid, stat_ids, {'replied': fields.datetime.now()}, context=context) + return stat_ids + + def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): + stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('bounced', '=', False)], context) + self.write(cr, uid, stat_ids, {'bounced': fields.datetime.now()}, context=context) + return stat_ids + diff --git a/addons/mass_mailing/models/res_config.py b/addons/mass_mailing/models/res_config.py new file mode 100644 index 00000000000..4b3757b7dca --- /dev/null +++ b/addons/mass_mailing/models/res_config.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +from openerp.osv import fields, osv + + +class MassMailingConfiguration(osv.TransientModel): + _name = 'marketing.config.settings' + _inherit = 'marketing.config.settings' + + _columns = { + 'group_mass_mailing_campaign': fields.boolean( + 'Manage Mass Mailing using Campaign', + implied_group='mass_mailing.group_mass_mailing_campaign', + help="""Manage mass mailign using Campaigns"""), + } diff --git a/addons/mass_mailing/security/ir.model.access.csv b/addons/mass_mailing/security/ir.model.access.csv index 59cf03c2c68..d5c76b9f69d 100644 --- a/addons/mass_mailing/security/ir.model.access.csv +++ b/addons/mass_mailing/security/ir.model.access.csv @@ -1,4 +1,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_mass_mailing_category,mail.mass_mailing.category,model_mail_mass_mailing_category,base.group_user,1,1,1,1
+access_mass_mailing_contact,mail.mass_mailing.contact,model_mail_mass_mailing_contact,base.group_user,1,1,1,1
+access_mass_mailing_list,mail.mass_mailing.list,model_mail_mass_mailing_list,base.group_user,1,1,1,1
+access_mass_mailing_stage,mail.mass_mailing.stage,model_mail_mass_mailing_stage,base.group_user,1,1,1,1
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,base.group_user,1,1,1,0
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,base.group_user,1,1,1,0
diff --git a/addons/mass_mailing/static/src/css/email_template.css b/addons/mass_mailing/static/src/css/email_template.css new file mode 100644 index 00000000000..495c201a172 --- /dev/null +++ b/addons/mass_mailing/static/src/css/email_template.css @@ -0,0 +1,17 @@ +.openerp .oe_kanban_email_template { + width: 360px; + min-height: 270px !important; +} + +.kanban_html_preview { + width: 600px; + height: 600px; + -webkit-transform: scale(.50); + -ms-transform: scale(.50); + transform: scale(.50); + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + margin: 0 0px -300px 0; + overflow: hidden !important; +} diff --git a/addons/mass_mailing/static/src/css/mass_mailing.css b/addons/mass_mailing/static/src/css/mass_mailing.css index e54ce46c025..cc91e8acf58 100644 --- a/addons/mass_mailing/static/src/css/mass_mailing.css +++ b/addons/mass_mailing/static/src/css/mass_mailing.css @@ -1,61 +1,13 @@ -.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_campaign { - /* Customize to manage content */ - width: 552px; - min-height: 278px !important; - /* End of customize */ +.openerp .oe_kanban_view .oe_kanban_mass_mailing_campaign { + width: 280px; + min-height: 141px; } -.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_segment { - /* Customize to manage content */ - width: 282px; - min-height: 246px !important; - /* End of customize */ +.openerp .oe_kanban_view .oe_kanban_mass_mailing_campaign .oe_kanban_header_right { + float: right; } -.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_stats { - width: 122px; /* Manage space in between stats */ - display: inline-block; - margin: 2px 5px 0px 5px; - text-align: center; - border: 1px solid rgba(0, 0, 0, 0.16); - -webkit-border-radius: 2px; - border-radius: 2px; - background-color: #FFFFFF; +.openerp .oe_kanban_view .oe_kanban_mass_mailing { + width: 280px; + min-height: 141px; } - -.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_result { - font-weight: bold; -} - -.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_gauge { - width: 120px; - height: 120px; - display: inline-block; -} - -.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_kanban_content div.oe_sparkline_container { - height: 60px; - width: 120px; - display: inline-block; - margin: 8px 5px 0px 5px; -} - -.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar_title { - text-align: center; - display: inline; -} - -.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar { - width: 100px; - height: 60px; - display: inline-block; -} - -/* - * Campaign related CSS - */ - - -/* - * Segment related CSS - */ diff --git a/addons/mass_mailing/static/src/js/mass_mailing.js b/addons/mass_mailing/static/src/js/mass_mailing.js index 93f63f01ad1..ef456c40bbf 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing.js +++ b/addons/mass_mailing/static/src/js/mass_mailing.js @@ -1,13 +1,13 @@ -openerp.mass_mailing = function(openerp) { +openerp.mass_mailing = function (instance) { + var _t = instance.web._t; openerp.web_kanban.KanbanRecord.include({ on_card_clicked: function (event) { if (this.view.dataset.model === 'mail.mass_mailing.campaign') { - this.$('.oe_mass_mailings a').first().click(); + this.$('.oe_mailings').click(); } else { this._super.apply(this, arguments); } }, }); - }; diff --git a/addons/mass_mailing/views/email_template.xml b/addons/mass_mailing/views/email_template.xml new file mode 100644 index 00000000000..cde5685abaf --- /dev/null +++ b/addons/mass_mailing/views/email_template.xml @@ -0,0 +1,97 @@ +<?xml version="1.0"?> +<openerp> + <data> + + <!-- Email Templates --> + <record model="ir.ui.view" id="email_template_form_minimal"> + <field name="name">email.template.form.minimal</field> + <field name="model">email.template</field> + <field name="priority">32</field> + <field name="arch" type="xml"> + <form string="Templates" version="7.0"> + <sheet> + <group> + <group> + <field name="name" required="True"/> + <field name="model_id" required="1" options="{'no_open': True, 'no_create': True}" + on_change="onchange_model_id(model_id)" + domain="[('model', 'in', ['res.partner', 'mail.mass_mailing.contact'])]"/> + <field name="model" invisible="True"/> + <field name="use_default_to" invisible="1"/> + </group> + <group> + <div class="oe_right oe_button_box" name="buttons"> + <button name="%(email_template.wizard_email_template_preview)d" string="Preview" + type="action" target="new" + context="{'template_id':active_id}"/> + <br /> + <!-- <field name="website_link" widget='html' radonly='1' + style='margin: 0px; padding: 0px;'/> --> + </div> + </group> + </group> + <notebook> + <page string="Body"> + <field name="body_html" nolabel="1"/> + <field name="attachment_ids" widget="many2many_binary"/> + </page> + </notebook> + </sheet> + </form> + </field> + </record> + + <record model="ir.ui.view" id="view_email_template_kanban"> + <field name="name">email.template.kanban</field> + <field name="model">email.template</field> + <field name="arch" type="xml"> + <kanban> + <field name="body_html"/> + <templates> + <t t-name="kanban-box"> + <div t-attf-class="oe_kanban_card oe_kanban_global_click oe_kanban_email_template"> + <div class="oe_dropdown_toggle oe_dropdown_kanban"> + <span class="oe_e">i</span> + <ul class="oe_dropdown_menu"> + <t t-if="widget.view.is_action_enabled('edit')"> + <li><a type="edit">Edit</a></li> + </t> + <t t-if="widget.view.is_action_enabled('delete')"> + <li><a type="delete">Delete</a></li> + </t> + </ul> + </div> + <div class="oe_kanban_content"> + <h3> + <field name="name"/> + </h3> + <div class="kanban_html_preview"> + <t t-raw="record.body_html.raw_value"/> + </div> + </div> + <div class="oe_clear"></div> + </div> + </t> + </templates> + </kanban> + </field> + </record> + + <record model="ir.actions.act_window" id="action_email_template_marketing"> + <field name="name">Templates</field> + <field name="res_model">email.template</field> + <field name="view_type">form</field> + <field name="view_mode">kanban,tree,form</field> + <field name="context">{ + 'form_view_ref': 'mass_mailing.email_template_form_minimal', + 'default_use_default_to': True, +}</field> + </record> + + <!-- Add Templates in Marketing / Mass mailing menu --> + <menuitem name="Mail Templates" id="menu_email_template" + parent="mass_mailing_campaign" sequence="3" + action="action_email_template_marketing"/> + + </data> +</openerp> diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml new file mode 100644 index 00000000000..7892feacacc --- /dev/null +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -0,0 +1,626 @@ +<?xml version="1.0"?> +<openerp> + <data> + + <!-- Marketing / Mass Mailing --> + <menuitem name="Mass Mailing" id="mass_mailing_campaign" + parent="base.marketing_menu" sequence="1"/> + <!-- Marketing / Mailing Lists --> + <menuitem name="Mailing Lists" id="mass_mailing_list" + parent="base.marketing_menu" sequence="2"/> + <!-- Marketing / Configuration --> + <menuitem name="Configuration" id="marketing_configuration" + parent="base.marketing_menu" sequence="99"/> + + <!-- MASS MAILING CONTACT --> + <record model="ir.ui.view" id="view_mail_mass_mailing_contact_search"> + <field name="name">mail.mass_mailing.contact.search</field> + <field name="model">mail.mass_mailing.contact</field> + <field name="arch" type="xml"> + <search string="Mailing Lists Subscribers"> + <field name="name"/> + <field name="email"/> + <field name="list_id"/> + <separator/> + <filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"/> + <group expand="0" string="Group By..."> + <filter string="Creation Date" name="group_create_date" + context="{'group_by': 'create_date'}"/> + <filter string="Mailing Lists" name="group_list_id" + context="{'group_by': 'list_id'}"/> + </group> + </search> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_contact_tree"> + <field name="name">mail.mass_mailing.contact.tree</field> + <field name="model">mail.mass_mailing.contact</field> + <field name="priority">10</field> + <field name="arch" type="xml"> + <tree string="Mailing Lists Subscribers" editable="top"> + <field name="email"/> + <field name="name"/> + <field name="list_id"/> + <field name="opt_out"/> + </tree> + </field> + </record> + + <record model="ir.actions.act_window" id="action_view_mass_mailing_contacts"> + <field name="name">Mailing List Subscribers</field> + <field name="res_model">mail.mass_mailing.contact</field> + <field name="view_type">form</field> + <field name="view_mode">tree</field> + <field name="context">{'search_default_not_opt_out': 1}</field> + </record> + + <record model="ir.actions.act_window" id="action_view_mass_mailing_contacts_from_list"> + <field name="name">Recipients</field> + <field name="res_model">mail.mass_mailing.contact</field> + <field name="view_type">form</field> + <field name="view_mode">tree</field> + <field name="context">{'search_default_list_id': active_id, 'search_default_not_opt_out': 1}</field> + <field name="help" type="html"> + <p class="oe_view_nocontent_create"> + Click to create a recipient. + </p> + </field> + </record> + + <menuitem name="Contacts" id="menu_email_mass_mailing_contacts" + parent="mass_mailing_list" sequence="50" + action="action_view_mass_mailing_contacts"/> + + <!-- MASS MAILING LIST --> + <record model="ir.ui.view" id="view_mail_mass_mailing_list_search"> + <field name="name">mail.mass_mailing.list.search</field> + <field name="model">mail.mass_mailing.list</field> + <field name="arch" type="xml"> + <search string="Mailing Lists"> + <field name="name"/> + </search> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_list_tree"> + <field name="name">mail.mass_mailing.list.tree</field> + <field name="model">mail.mass_mailing.list</field> + <field name="priority">10</field> + <field name="arch" type="xml"> + <tree string="Mailing Lists"> + <field name="name"/> + <field name="contact_nbr"/> + </tree> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_list_form"> + <field name="name">mail.mass_mailing.list.form</field> + <field name="model">mail.mass_mailing.list</field> + <field name="arch" type="xml"> + <form string="Contact List" version="7.0"> + <sheet> + <div class="oe_right oe_button_box" name="buttons"> + <button name="%(mass_mailing.action_view_mass_mailing_contacts_from_list)d" + type="action" icon="fa-user" class="oe_stat_button pull-right"> + <field name="contact_nbr" string="Recipients" widget="statinfo"/> + </button> + </div> + <div class="oe_title"> + <label for="name" class="oe_edit_only"/> + <h1> + <field name="name"/> + </h1> + </div> + </sheet> + </form> + </field> + </record> + + <record model="ir.actions.act_window" id="action_view_mass_mailing_lists"> + <field name="name">Contact Lists</field> + <field name="res_model">mail.mass_mailing.list</field> + <field name="view_type">form</field> + <field name="view_mode">tree,form</field> + <field name="help" type="html"> +<p class="oe_view_nocontent_create"> + Click here to create a new mailing list. +</p><p> + Mailing lists allows you to to manage customers and + contacts easily and to send to mailings in a single click. +</p></field> + </record> + + <menuitem name="Mailing Lists" id="menu_email_mass_mailing_lists" + parent="mass_mailing_list" sequence="40" + action="action_view_mass_mailing_lists"/> + + <!-- MASS MAILING !--> + <record model="ir.ui.view" id="view_mail_mass_mailing_search"> + <field name="name">mail.mass_mailing.search</field> + <field name="model">mail.mass_mailing</field> + <field name="arch" type="xml"> + <search string="Mass Mailings"> + <field name="name" string="Mailings"/> + <field name="mass_mailing_campaign_id"/> + <group expand="0" string="Group By..."> + <filter string="State" name="group_state" + context="{'group_by': 'state'}"/> + <filter string="Campaign" name="group_mass_mailing_campaign_id" + groups="mass_mailing.group_mass_mailing_campaign" + context="{'group_by': 'mass_mailing_campaign_id'}"/> + </group> + </search> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_tree"> + <field name="name">mail.mass_mailing.tree</field> + <field name="model">mail.mass_mailing</field> + <field name="priority">10</field> + <field name="arch" type="xml"> + <tree string="Mass Mailings"> + <field name="name"/> + <field name="sent"/> + <field name="delivered"/> + <field name="opened"/> + <field name="replied"/> + <field name="mass_mailing_campaign_id" + groups="mass_mailing.group_mass_mailing_campaign"/> + </tree> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_form"> + <field name="name">mail.mass_mailing.form</field> + <field name="model">mail.mass_mailing</field> + <field name="arch" type="xml"> + <form string="Mass Mailing" version="7.0"> + <header> + <button name="action_test_mailing" type="object" + class="oe_highlight" string="Test Mailing" states="draft"/> + <button name="send_mail" type="object" states="draft,test" + class="oe_highlight" string="Send to All"/> + <button name="action_test_mailing" type="object" states="test,done" + string="Send Test Sample"/> + <field name="state" widget="statusbar"/> + </header> + <div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('scheduled', '=', 0)]}"> + <p><strong> + <field name="scheduled" class="oe_inline"/> + emails are in queue and will be sent soon. + </strong></p> + </div> + <sheet> + <div class="oe_button_box pull-right" attrs="{'invisible': [('state', 'in', ('draft','test'))]}"> + <button name="%(action_view_mass_mailing_contacts)d" + type="action" class="oe_stat_button"> + <field name="received_ratio" string="Received" widget="percentpie"/> + </button> + <button name="%(action_view_mass_mailing_contacts)d" + type="action" class="oe_stat_button"> + <field name="opened_ratio" string="Opened" widget="percentpie"/> + </button> + <button name="%(action_view_mass_mailing_contacts)d" + type="action" class="oe_stat_button"> + <field name="replied_ratio" string="Replied" widget="percentpie"/> + </button> + <button name="%(action_view_mass_mailing_contacts)d" + type="action" class="oe_stat_button oe_inline"> + <field name="opened_dayly" string="Opened Daily" widget="barchart"/> + </button> + <button name="%(action_view_mass_mailing_contacts)d" + type="action" class="oe_stat_button oe_inline"> + <field name="replied_dayly" string="Replied Daily" widget="barchart"/> + </button> + </div> + <div class="oe_button_box" attrs="{'invisible': [('total', '=', 0)]}" style="margin-bottom: 32px"> + <button name="%(action_view_mass_mailing_contacts)d" type="action" + icon="fa-envelope-o" class="oe_stat_button"> + <field name="total" string="Emails" widget="statinfo"/> + </button> + </div> + + <group> + <field name="email_from"/> + <field name="name"/> + <label for="mailing_model" string="Recipients"/> + <div> + <field name="mailing_model" widget="radio" style="margin-bottom: 8px" + on_change="on_change_model_and_list(mailing_model, contact_list_ids)"/> + + <field name="mailing_domain" widget="char_domain" + placeholder="Select recipients" + options="{'model_field': 'mailing_model'}"/> + + <div attrs="{'invisible': [('mailing_model', '<>', 'mail.mass_mailing.contact')]}"> + <label for="contact_list_ids" string="Select mailing lists:" class="oe_edit_only"/> + <field name="contact_list_ids" widget="many2many_tags" + placeholder="Select mailing lists..." class="oe_inline" + on_change="on_change_model_and_list(mailing_model, contact_list_ids)"/> + </div> + </div> + </group> + <notebook> + <page string="Mail Body"> + <button name="action_edit_html" type="object" string="Design Email" class="oe_highlight" states="draft"/> + <button name="action_edit_html" type="object" string="Change Email Design" states="test"/> + <div attrs="{'invisible' : ['|', '|', ('state', '=', 'done'), ('body_html','!=',False), ('mailing_domain', '=', False)]}" class="oe_view_nocontent oe_clear"> + <p class="oe_view_nocontent_create oe_edit_only"> + Click to design your email. + </p> + </div> + <field name="body_html" readonly="1"/> + <field name="attachment_ids" widget="many2many_binary" string="Attach a file"/> + </page> + <page string="Options"> + <group> + <group string="Mailing"> + <label for="reply_to"/> + <div> + <p class="alert alert-danger" + attrs="{'invisible': ['|', ('reply_to_mode', '!=', 'thread'), ('mailing_model', 'not in', ['mail.mass_mailing.contact', 'res.partner'])]}"> + This option is not available for the recipients you selected. + Please use a specific reply-to email address. + </p> + <field name="reply_to_mode" widget="radio"/> + <field name="reply_to" style="margin-left: 16px;" + attrs="{'required': [('reply_to_mode', '=', 'email')]}"/> + </div> + <field name="create_date" readonly="1"/> + <field name="sent_date" readonly="1"/> + </group> + <group string="Campaign"> + <field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/> + <label for="contact_ab_pc" groups="mass_mailing.group_mass_mailing_campaign"/> + <div> + <field name="contact_ab_pc" class="oe_inline"/> % + </div> + </group> + </group> + </page> + </notebook> + </sheet> + </form> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_kanban"> + <field name="name">mail.mass_mailing.kanban</field> + <field name="model">mail.mass_mailing</field> + <field name="arch" type="xml"> + <kanban default_group_by='state'> + <field name='color'/> + <field name='total'/> + <templates> + <t t-name="kanban-box"> + <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing"> + <div class="oe_dropdown_toggle oe_dropdown_kanban"> + <span class="oe_e">i</span> + <ul class="oe_dropdown_menu"> + <t t-if="widget.view.is_action_enabled('delete')"> + <li><a type="delete">Delete</a></li> + </t> + </ul> + </div> + <div class="oe_kanban_content"> + <div> + <h3><field name="name"/></h3> + <h4 style="display: inline;"><field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/></h4> + <t t-if="record.mass_mailing_campaign_id.raw_value" groups="mass_mailing.group_mass_mailing_campaign"> - </t><field name="sent_date"/> + </div> + <div> + <div style="display: inline-block"> + <field name="delivered" widget="gauge" style="width:120px; height: 90px;" + options="{'max_field': 'total'}"/> + </div> + <div style="display: inline-block; vertical-align: top;"> + <strong>Opened</strong> <field name="opened_ratio"/> %<br /> + <strong>Replied</strong> <field name="replied_ratio"/> % + </div> + </div> + </div> + <div class="oe_clear"></div> + </div> + </t> + </templates> + </kanban> + </field> + </record> + + <record id="action_view_mass_mailings" model="ir.actions.act_window"> + <field name="name">Mass Mailings</field> + <field name="res_model">mail.mass_mailing</field> + <field name="view_type">form</field> + <field name="view_mode">kanban,tree,form</field> + <field name="help" type="html"> +<p class="oe_view_nocontent_create"> + Click here to create a new mailing. +</p><p> + Mass mailing allows you to to easily design and send mass mailings to your contacts, customers or leads using mailing lists. +</p></field> + </record> + + <record id="action_view_mass_mailings_from_campaign" model="ir.actions.act_window"> + <field name="name">Mass Mailings</field> + <field name="res_model">mail.mass_mailing</field> + <field name="view_type">form</field> + <field name="view_mode">kanban,tree,form</field> + <field name="context">{ + 'search_default_mass_mailing_campaign_id': [active_id], + 'default_mass_mailing_campaign_id': active_id, + } + </field> + <field name="help" type="html"> +<p class="oe_view_nocontent_create"> + Click here to create a new mailing. +</p><p> + Mass mailing allows you to to easily design and send mass mailings to your contacts, customers or leads using mailing lists. +</p></field> + </record> + + <menuitem name="Mass Mailings" id="menu_email_mass_mailings" + parent="mass_mailing_campaign" sequence="2" + action="action_view_mass_mailings"/> + + <!-- MASS MAILING CAMPAIGN STAGE !--> + <record model="ir.ui.view" id="view_mail_mass_mailing_stage_search"> + <field name="name">mail.mass_mailing.stage.search</field> + <field name="model">mail.mass_mailing.stage</field> + <field name="arch" type="xml"> + <search string="Mass Mailings"> + <field name="name"/> + </search> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_stage_tree"> + <field name="name">mail.mass_mailing.stage.tree</field> + <field name="model">mail.mass_mailing.stage</field> + <field name="priority">10</field> + <field name="arch" type="xml"> + <tree string="Mass Mailings" editable="top"> + <field name="sequence" widget="handle"/> + <field name="name"/> + </tree> + </field> + </record> + <record id="action_view_mass_mailing_stages" model="ir.actions.act_window"> + <field name="name">Mass Mailing Stages</field> + <field name="res_model">mail.mass_mailing.stage</field> + <field name="view_type">form</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem name="Campaign Stages" id="menu_view_mass_mailing_stages" + parent="marketing_configuration" sequence="1" + groups="mass_mailing.group_mass_mailing_campaign" + action="action_view_mass_mailing_stages"/> + + <!-- MASS MAILING CAMPAIGNS !--> + <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_search"> + <field name="name">mail.mass_mailing.campaign.search</field> + <field name="model">mail.mass_mailing.campaign</field> + <field name="arch" type="xml"> + <search string="Mass Mailing Campaigns"> + <field name="name" string="Campaigns"/> + <field name="category_ids"/> + <field name="user_id"/> + <group expand="0" string="Group By..."> + <filter string="Stage" name="group_stage_id" + context="{'group_by': 'stage_id'}"/> + <filter string="Responsible" name="group_user_id" + context="{'group_by': 'user_id'}"/> + </group> + </search> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_tree"> + <field name="name">mail.mass_mailing.campaign.tree</field> + <field name="model">mail.mass_mailing.campaign</field> + <field name="priority">10</field> + <field name="arch" type="xml"> + <tree string="Mass Mailing Campaigns"> + <field name="name"/> + <field name="user_id"/> + <field name="stage_id"/> + <field name="category_ids"/> + </tree> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form"> + <field name="name">mail.mass_mailing.campaign.form</field> + <field name="model">mail.mass_mailing.campaign</field> + <field name="arch" type="xml"> + <form string="Mass Mailing Campaign" version="7.0"> + <header> + <field name="stage_id" widget="statusbar" clickable="True"/> + </header> + <sheet> + <group> + <group> + <field name="name"/> + <field name="user_id"/> + <field name="category_ids" widget="many2many_tags"/> + </group> + <group> + <field name="total" invisible="1"/> + <div class="oe_right oe_button_box" name="buttons" + attrs="{'invisible': [('total', '=', 0)]}"> + <button name="%(action_view_mass_mailing_contacts)d" + type="action" class="oe_stat_button oe_inline"> + <field name="received_ratio" widget="percentpie" string="Received"/> + </button> + <button name="%(action_view_mass_mailing_contacts)d" + type="action" class="oe_stat_button oe_inline"> + <field name="opened_ratio" widget="percentpie" string="Opened"/> + </button> + <button name="%(action_view_mass_mailing_contacts)d" + type="action" class="oe_stat_button oe_inline"> + <field name="replied_ratio" widget="percentpie" string="Replied"/> + </button> + </div> + </group> + </group> + <strong>Related Mailing(s)</strong> + <field name="mass_mailing_ids" readonly="1" string="Related Mailing(s)"> + <tree> + <field name="name"/> + <field name="sent_date"/> + <field name="state"/> + <field name="delivered"/> + <field name="opened"/> + <field name="replied"/> + <field name="bounced"/> + <button name="action_duplicate" type="object" string="Duplicate"/> + </tree> + </field> + </sheet> + </form> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_kanban"> + <field name="name">mail.mass_mailing.campaign.kanban</field> + <field name="model">mail.mass_mailing.campaign</field> + <field name="arch" type="xml"> + <kanban default_group_by='stage_id'> + <field name='total'/> + <field name='color'/> + <field name='user_id'/> + <field name='mass_mailing_ids'/> + <templates> + <t t-name="kanban-box"> + <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing_campaign"> + <div class="oe_dropdown_toggle oe_dropdown_kanban"> + <span class="oe_e">i</span> + <ul class="oe_dropdown_menu"> + <t t-if="widget.view.is_action_enabled('edit')"> + <li><a type="edit">Settings</a></li> + </t> + <t t-if="widget.view.is_action_enabled('delete')"> + <li><a type="delete">Delete</a></li> + </t> + <li><ul class="oe_kanban_colorpicker" data-field="color"/></li> + </ul> + </div> + <div class="oe_kanban_content"> + <div> + <img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" + t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar oe_kanban_header_right"/> + <h3 style="margin-bottom: 8px;"><field name="name"/></h3> + <field name="category_ids"/> + <a name="%(action_view_mass_mailings_from_campaign)d" type="action" + class="oe_mailings"> + <h4 style="margin-top: 8px;"><t t-raw="record.mass_mailing_ids.raw_value.length"/> Mailings</h4> + </a> + </div> + <div class="oe_clear"></div> + <div> + <div style="display: inline-block"> + <field name="delivered" widget="gauge" style="width:120px; height: 90px;" + options="{'max_field': 'total'}"/> + </div> + <div style="display: inline-block; vertical-align: top;"> + <strong>Opened</strong> <field name="opened_ratio"/> %<br /> + <strong>Replied</strong> <field name="replied_ratio"/> % + </div> + </div> + </div> + <div class="oe_clear"></div> + </div> + </t> + </templates> + </kanban> + </field> + </record> + + <record id="action_view_mass_mailing_campaigns" model="ir.actions.act_window"> + <field name="name">Mass Mailing Campaigns</field> + <field name="res_model">mail.mass_mailing.campaign</field> + <field name="view_type">form</field> + <field name="view_mode">kanban,tree,form</field> + <field name="help" type="html"> + <p class="oe_view_nocontent_create"> + Click to define a new mass mailing campaign. + </p><p> + Create a campaign to structure mass mailing and get analysis from email status. + </p> + </field> + </record> + + <menuitem name="Campaigns" id="menu_email_campaigns" + parent="mass_mailing_campaign" sequence="1" + action="action_view_mass_mailing_campaigns" + groups="mass_mailing.group_mass_mailing_campaign"/> + + <!-- MAIL MAIL STATISTICS !--> + <record model="ir.ui.view" id="view_mail_mail_statistics_search"> + <field name="name">mail.mail.statistics.search</field> + <field name="model">mail.mail.statistics</field> + <field name="arch" type="xml"> + <search string="Mail Statistics"> + <field name="mail_mail_id"/> + <field name="message_id"/> + </search> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mail_statistics_tree"> + <field name="name">mail.mail.statistics.tree</field> + <field name="model">mail.mail.statistics</field> + <field name="arch" type="xml"> + <tree string="Mail Statistics"> + <field name="mail_mail_id"/> + <field name="message_id"/> + <field name="sent"/> + <field name="opened"/> + <field name="replied"/> + <field name="bounced"/> + </tree> + </field> + </record> + + <record model="ir.ui.view" id="view_mail_mail_statistics_form"> + <field name="name">mail.mail.statistics.form</field> + <field name="model">mail.mail.statistics</field> + <field name="arch" type="xml"> + <form string="Mail Statistics" version="7.0"> + <group> + <group> + <field name="mail_mail_id"/> + <field name="message_id"/> + <field name="exception"/> + <field name="sent"/> + <field name="opened"/> + <field name="replied"/> + <field name="bounced"/> + </group> + <group> + <field name="mass_mailing_id"/> + <field name="mass_mailing_campaign_id"/> + <field name="model"/> + <field name="res_id"/> + </group> + </group> + </form> + </field> + </record> + + <record id="action_view_mail_mail_statistics" model="ir.actions.act_window"> + <field name="name">Mail Statistics</field> + <field name="res_model">mail.mail.statistics</field> + <field name="view_type">form</field> + <field name="view_mode">tree,form</field> + </record> + + <!-- Add in Technical/Email --> + <menuitem name="Mail Statistics" id="menu_email_statistics" + parent="base.menu_email" sequence="50" + action="action_view_mail_mail_statistics"/> + + </data> +</openerp> diff --git a/addons/mass_mailing/views/res_config.xml b/addons/mass_mailing/views/res_config.xml new file mode 100644 index 00000000000..0f61086e2cc --- /dev/null +++ b/addons/mass_mailing/views/res_config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<openerp> + <data> + + <record id="view_marketing_configuration_mass_mailing" model="ir.ui.view"> + <field name="name">marketing.config.settings.mass.mailing</field> + <field name="model">marketing.config.settings</field> + <field name="inherit_id" ref="marketing.view_marketing_configuration"/> + <field name="arch" type="xml"> + <xpath expr="//div[@name='module_mass_mailing']" position="after"> + <div> + <field name="group_mass_mailing_campaign" class="oe_inline"/> + <label for="group_mass_mailing_campaign"/> + </div> + </xpath> + </field> + </record> + + </data> +</openerp> diff --git a/addons/mass_mailing/views/res_partner.xml b/addons/mass_mailing/views/res_partner.xml new file mode 100644 index 00000000000..14710cf8f37 --- /dev/null +++ b/addons/mass_mailing/views/res_partner.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<openerp> + <data> + + </data> +</openerp> diff --git a/addons/mass_mailing/wizard/__init__.py b/addons/mass_mailing/wizard/__init__.py index 669d12289c2..aa8074b7488 100644 --- a/addons/mass_mailing/wizard/__init__.py +++ b/addons/mass_mailing/wizard/__init__.py @@ -1,23 +1,4 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> -# -############################################################################## +import test_mailing import mail_compose_message -import mail_mass_mailing_create_segment diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py index 4b3f0d6b9bd..4aebff2d04d 100644 --- a/addons/mass_mailing/wizard/mail_compose_message.py +++ b/addons/mass_mailing/wizard/mail_compose_message.py @@ -1,23 +1,4 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> -# -############################################################################## from openerp.osv import osv, fields @@ -29,11 +10,14 @@ class MailComposeMessage(osv.TransientModel): _columns = { 'mass_mailing_campaign_id': fields.many2one( - 'mail.mass_mailing.campaign', 'Mass mailing campaign', + 'mail.mass_mailing.campaign', 'Mass Mailing Campaign', ), 'mass_mailing_id': fields.many2one( - 'mail.mass_mailing', 'Mass mailing', - domain="[('mass_mailing_campaign_id', '=', mass_mailing_campaign_id)]", + 'mail.mass_mailing', 'Mass Mailing' + ), + 'mass_mailing_name': fields.char('Mass Mailing'), + 'mailing_list_ids': fields.many2many( + 'mail.mass_mailing.list', string='Mailing List' ), } @@ -42,23 +26,27 @@ class MailComposeMessage(osv.TransientModel): mail.mail.statistics values in the o2m of mail_mail, when doing pure email mass mailing. """ res = super(MailComposeMessage, self).get_mail_values(cr, uid, wizard, res_ids, context=context) - if wizard.composition_mode == 'mass_mail' and wizard.mass_mailing_campaign_id: # TODO: which kind of mass mailing ? - if wizard.mass_mailing_id: - mass_mailing_id = wizard.mass_mailing_id.id - else: - current_date = fields.datetime.now() + # use only for allowed models in mass mailing + if wizard.composition_mode == 'mass_mail' and \ + (wizard.mass_mailing_name or wizard.mass_mailing_id) and \ + wizard.model in [item[0] for item in self.pool['mail.mass_mailing']._get_mailing_model(cr, uid, context=context)]: + mass_mailing = wizard.mass_mailing_id + if not mass_mailing: mass_mailing_id = self.pool['mail.mass_mailing'].create( cr, uid, { - 'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id, - 'name': '%s-%s' % (wizard.mass_mailing_campaign_id.name, current_date), - 'date': current_date, - 'domain': wizard.active_domain, + 'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id and wizard.mass_mailing_campaign_id.id or False, + 'name': wizard.mass_mailing_name, 'template_id': wizard.template_id and wizard.template_id.id or False, + 'state': 'done', + 'mailing_type': wizard.model, + 'mailing_domain': wizard.active_domain, }, context=context) + mass_mailing = self.pool['mail.mass_mailing'].browse(cr, uid, mass_mailing_id, context=context) for res_id in res_ids: + res[res_id]['mailing_id'] = mass_mailing.id res[res_id]['statistics_ids'] = [(0, 0, { 'model': wizard.model, 'res_id': res_id, - 'mass_mailing_id': mass_mailing_id, + 'mass_mailing_id': mass_mailing.id, })] return res diff --git a/addons/mass_mailing/wizard/mail_compose_message_view.xml b/addons/mass_mailing/wizard/mail_compose_message_view.xml index f5dbf57a808..61cfe823006 100644 --- a/addons/mass_mailing/wizard/mail_compose_message_view.xml +++ b/addons/mass_mailing/wizard/mail_compose_message_view.xml @@ -9,7 +9,9 @@ <field name="inherit_id" ref="mail.email_compose_message_wizard_form"/> <field name="arch" type="xml"> <xpath expr="//field[@name='notify']" position="after"> - <field name="mass_mailing_campaign_id" + <field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign" + attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/> + <field name="mass_mailing_name" attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/> </xpath> </field> diff --git a/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.py b/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.py deleted file mode 100644 index 0944e89f1de..00000000000 --- a/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> -# -############################################################################## - -from openerp.osv import osv, fields - -from openerp.tools.translate import _ - - -class MailMassMailingCreate(osv.TransientModel): - """Wizard to help creating mass mailing waves for a campaign. """ - - _name = 'mail.mass_mailing.create' - _description = 'Mass mailing creation' - - _columns = { - 'mass_mailing_campaign_id': fields.many2one( - 'mail.mass_mailing.campaign', 'Mass mailing campaign', - required=True, - ), - 'model_id': fields.many2one( - 'ir.model', 'Document Type', - required=True, - help='Document on which the mass mailing will run. This must be a ' - 'valid OpenERP model.', - ), - 'model_model': fields.related( - 'model_id', 'name', - type='char', string='Model Name' - ), - 'filter_id': fields.many2one( - 'ir.filters', 'Filter', - required=True, - domain="[('model_id', '=', model_model)]", - help='Filter to be applied on the document to find the records to be ' - 'mailed.', - ), - 'domain': fields.related( - 'filter_id', 'domain', - type='char', string='Domain', - ), - 'template_id': fields.many2one( - 'email.template', 'Template', required=True, - domain="[('model_id', '=', model_id)]", - ), - 'name': fields.char( - 'Mailing Name', required=True, - help='Name of the mass mailing.', - ), - 'mass_mailing_id': fields.many2one( - 'mail.mass_mailing', 'Mass Mailing', - ), - } - - def _get_default_model_id(self, cr, uid, context=None): - model_ids = self.pool['ir.model'].search(cr, uid, [('model', '=', 'res.partner')], context=context) - return model_ids and model_ids[0] or False - - _defaults = { - 'model_id': lambda self, cr, uid, ctx=None: self._get_default_model_id(cr, uid, context=ctx), - } - - def on_change_model_id(self, cr, uid, ids, model_id, context=None): - if model_id: - model_model = self.pool['ir.model'].browse(cr, uid, model_id, context=context).model - else: - model_model = False - return {'value': {'model_model': model_model}} - - def on_change_filter_id(self, cr, uid, ids, filter_id, context=None): - if filter_id: - domain = self.pool['ir.filters'].browse(cr, uid, filter_id, context=context).domain - else: - domain = False - return {'value': {'domain': domain}} - - def create_mass_mailing(self, cr, uid, ids, context=None): - """ Create a mass mailing based on wizard data, and update the wizard """ - for wizard in self.browse(cr, uid, ids, context=context): - mass_mailing_values = { - 'name': wizard.name, - 'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id, - 'domain': wizard.domain, - 'template_id': wizard.template_id.id, - } - mass_mailing_id = self.pool['mail.mass_mailing'].create(cr, uid, mass_mailing_values, context=context) - self.write(cr, uid, [wizard.id], {'mass_mailing_id': mass_mailing_id}, context=context) - return True - - def launch_composer(self, cr, uid, ids, context=None): - """ Main wizard action: create a new mailing and launch the mail.compose.message - email composer with wizard data. """ - self.create_mass_mailing(cr, uid, ids, context=context) - - wizard = self.browse(cr, uid, ids[0], context=context) - ctx = dict(context) - ctx.update({ - 'default_composition_mode': 'mass_mail', - 'default_template_id': wizard.template_id.id, - 'default_use_mass_mailing_campaign': True, - 'default_use_active_domain': True, - 'default_model': wizard.model_id.model, - 'default_res_id': False, - 'default_active_domain': wizard.domain, - 'default_mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id, - 'default_mass_mailing_id': wizard.mass_mailing_id.id, - }) - return { - 'name': _('Compose Email for Mass Mailing'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'mail.compose.message', - 'views': [(False, 'form')], - 'view_id': False, - 'target': 'new', - 'context': ctx, - } diff --git a/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.xml b/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.xml deleted file mode 100644 index b2b48fb8618..00000000000 --- a/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.xml +++ /dev/null @@ -1,82 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<openerp> - <data> - - <!-- Wizard form view --> - <record model="ir.ui.view" id="view_mail_mass_mailing_create_form"> - <field name="name">mail.mass_mailing.create.form</field> - <field name="model">mail.mass_mailing.create</field> - <field name="arch" type="xml"> - <form string="Create a Mass Mailing" version="7.0"> - <group> - <field name="model_model" invisible="1"/> - <field name="domain" invisible="1"/> - - <label for="mass_mailing_campaign_id"/> - <div> - <field name="mass_mailing_campaign_id"/> - <p class="oe_grey" - attrs="{'invisible': [('mass_mailing_campaign_id', '!=', False)]}"> - Please choose a mass mailing campaign that will hold the new mailing. - </p> - </div> - - <label for="model_id"/> - <div> - <field name="model_id" - on_change="on_change_model_id(model_id, context)"/> - <p class="oe_grey" - attrs="{'invisible': [('model_id', '!=', False)]}"> - Please choose a model on which you will run the mass mailing. - </p> - </div> - - <label for="filter_id"/> - <div> - <field name="filter_id" - on_change="on_change_filter_id(filter_id, context)"/> - <p class="oe_grey" - attrs="{'invisible': [('filter_id', '!=', False)]}"> - Please choose a filter that will be applied on the model - to find the records on which you will run the mass mailing. - </p> - </div> - - <label for="template_id"/> - <div> - <field name="template_id"/> - <p class="oe_grey" - attrs="{'invisible': [('template_id', '!=', False)]}"> - Please choose the template to use to render the emails - to send. - </p> - </div> - - <label for="name"/> - <div> - <field name="name"/> - <p class="oe_grey"> - Please choose the name of the mailing. - </p> - </div> - - <button name="launch_composer" type="object" - string="Create mailing and launch email composer"/> - </group> - </form> - </field> - </record> - - <record model="ir.actions.act_window" id="action_mail_mass_mailing_create"> - <field name="name">Create Mass Mailing</field> - <field name="res_model">mail.mass_mailing.create</field> - <field name="src_model">mail.mass_mailing.campaign</field> - <field name="type">ir.actions.act_window</field> - <field name="view_type">form</field> - <field name="view_mode">form</field> - <field name="target">new</field> - <field name="context">{'default_mass_mailing_campaign_id': active_id}</field> - </record> - - </data> -</openerp> diff --git a/addons/mass_mailing/wizard/test_mailing.py b/addons/mass_mailing/wizard/test_mailing.py new file mode 100644 index 00000000000..bd1124a8cdc --- /dev/null +++ b/addons/mass_mailing/wizard/test_mailing.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +from openerp import tools +from openerp.osv import osv, fields + + +class TestMassMailing(osv.TransientModel): + _name = 'mail.mass_mailing.test' + _description = 'Sample Mail Wizard' + + _columns = { + 'email_to': fields.char('Recipients', required=True, + help='Comma-separated list of email addresses.'), + 'mass_mailing_id': fields.many2one('mail.mass_mailing', 'Mailing', required=True), + } + + _defaults = { + 'email_to': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx), + } + + def send_mail_test(self, cr, uid, ids, context=None): + Mail = self.pool['mail.mail'] + for wizard in self.browse(cr, uid, ids, context=context): + mailing = wizard.mass_mailing_id + test_emails = tools.email_split(wizard.email_to) + mail_ids = [] + for test_mail in test_emails: + mail_values = { + 'email_from': mailing.email_from, + 'reply_to': mailing.reply_to, + 'email_to': test_mail, + 'subject': mailing.name, + 'body_html': mailing.body_html, + 'auto_delete': True, + 'mailing_id': wizard.mass_mailing_id.id, + } + mail_ids.append(Mail.create(cr, uid, mail_values, context=context)) + Mail.send(cr, uid, mail_ids, context=context) + self.pool['mail.mass_mailing'].write(cr, uid, [mailing.id], {'state': 'test'}, context=context) + return True diff --git a/addons/mass_mailing/wizard/test_mailing.xml b/addons/mass_mailing/wizard/test_mailing.xml new file mode 100644 index 00000000000..61ea8817bc4 --- /dev/null +++ b/addons/mass_mailing/wizard/test_mailing.xml @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<openerp> + <data> + + <record model="ir.ui.view" id="view_mail_mass_mailing_test_form"> + <field name="name">mail.mass_mailing.test.form</field> + <field name="model">mail.mass_mailing.test</field> + <field name="arch" type="xml"> + <form string="Send a Sample Mail" version="7.0"> + <p class="text-muted"> + Send a sample of this mailing to the above of email addresses for test purpose. + </p> + <group> + <field name="email_to"/> + </group> + <footer> + <button string="Send Sample Mail" name="send_mail_test" type="object" class="oe_highlight"/> + or + <button string="Cancel" class="oe_link" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record id="action_mail_mass_mailing_test" model="ir.actions.act_window"> + <field name="name">Mailing Test</field> + <field name="res_model">mail.mass_mailing.test</field> + <field name="view_type">form</field> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> + + </data> +</openerp> |
