[IMP] Adding stats file
authorFabien Pinckaers <fp@tinyerp.com>
Sat, 12 Apr 2014 23:25:48 +0000 (01:25 +0200)
committerFabien Pinckaers <fp@tinyerp.com>
Sat, 12 Apr 2014 23:25:48 +0000 (01:25 +0200)
bzr revid: fp@tinyerp.com-20140412232548-csyb7j13fyu4cwnw

addons/mass_mailing/models/mass_mailing_stats.py [new file with mode: 0644]
addons/mass_mailing/views/mass_mailing.xml

diff --git a/addons/mass_mailing/models/mass_mailing_stats.py b/addons/mass_mailing/models/mass_mailing_stats.py
new file mode 100644 (file)
index 0000000..a9134c5
--- /dev/null
@@ -0,0 +1,350 @@
+# -*- 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 MassMailingList(osv.Model):
+    _inherit = 'mail.mass_mailing.list'
+    def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
+        result = dict.fromkeys(ids, 0)
+        mlc = self.pool.get('mail.mass_mailing.contact')
+        for m in mlc.read_group(cr, uid, [('list_id','in',ids)], ['list_id'], ['list_id'], context=context):
+            result[m['list_id'][0]] = m['list_id_count']
+        return result
+
+    _columns = {
+        'contact_nbr': fields.function(
+            _get_contact_nbr, type='integer',
+            string='Number of Contacts',
+        ),
+    }
+
+
+class MassMailingCampaign(osv.Model):
+    _inherit = "mail.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]['sent'] or 1)
+            results[cid]['opened_ratio'] = 100.0 * results[cid]['opened'] / (results[cid]['sent'] or 1)
+            results[cid]['replied_ratio'] = 100.0 * results[cid]['replied'] / (results[cid]['sent'] or 1)
+        return results
+
+    _columns = {
+        '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',
+        ),
+    }
+
+
+class MassMailing(osv.Model):
+    _inherit = 'mail.mass_mailing'
+    _period_number = 6
+    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_dayly'] = json.dumps(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_dayly'] = json.dumps(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 """
+        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]['sent'] or 1)
+            results[mid]['opened_ratio'] = 100.0 * results[mid]['opened'] / (results[mid]['sent'] or 1)
+            results[mid]['replied_ratio'] = 100.0 * results[mid]['replied'] / (results[mid]['sent'] or 1)
+        return results
+
+    # To improve
+    def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
+        res = dict.fromkeys(ids, False)
+        for mailing in self.browse(cr, uid, ids, context=context):
+            if not mailing.mailing_domain:
+                res[mailing.id] = 0
+                continue
+            print mailing.mailing_model, mailing.mailing_domain
+            res[mailing.id] = self.pool[mailing.mailing_model].search(
+                cr, uid, eval(mailing.mailing_domain), count=True, context=context
+            )
+        return res
+
+    _columns = {
+        'contact_nbr': fields.function(_get_contact_nbr, type='integer', string='Contact Number'),
+        # 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',
+        ),
+    }
+
+
+
+
+# Merge this on emails
+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
+
index 52f55ea..6e1f73a 100644 (file)
                             class="oe_highlight" string="Send to All"/>
                         <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_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>
 
                         <div class="oe_button_box pull-right" attrs="{'invisible': [('total', '=', 0)]}">
                             <button name="%(action_mail_mass_mailing_report)d"