[MERGE] Merged fp's refactoring branch.

bzr revid: tde@openerp.com-20140414131109-7ijlz4gybjd2b2oj
This commit is contained in:
Thibault Delavallée 2014-04-14 15:11:09 +02:00
commit 55266ab25d
23 changed files with 570 additions and 859 deletions

View File

@ -44,7 +44,6 @@ professional emails and reuse templates in a few clicks.
'data/mass_mailing_data.xml', 'data/mass_mailing_data.xml',
'wizard/mail_compose_message_view.xml', 'wizard/mail_compose_message_view.xml',
'wizard/test_mailing.xml', 'wizard/test_mailing.xml',
'wizard/mailing_list_confirm.xml',
'views/mass_mailing_report.xml', 'views/mass_mailing_report.xml',
'views/mass_mailing.xml', 'views/mass_mailing.xml',
'views/res_config.xml', 'views/res_config.xml',
@ -55,7 +54,9 @@ professional emails and reuse templates in a few clicks.
'js': [ 'js': [
'static/src/js/mass_mailing.js', 'static/src/js/mass_mailing.js',
], ],
'qweb': [], 'qweb' : [
'static/src/xml/*.xml',
],
'css': [ 'css': [
'static/src/css/mass_mailing.css', 'static/src/css/mass_mailing.css',
'static/src/css/email_template.css' 'static/src/css/email_template.css'

View File

@ -14,7 +14,6 @@
<field name="auto_delete" eval="False"/> <field name="auto_delete" eval="False"/>
<field name="partner_to">${object.id}</field> <field name="partner_to">${object.id}</field>
<field name="reply_to"><![CDATA[Info <info@yourcompany.example.com>]]></field> <field name="reply_to"><![CDATA[Info <info@yourcompany.example.com>]]></field>
<field name="use_in_mass_mailing" eval="True"/>
<field name="use_default_to" eval="True"/> <field name="use_default_to" eval="True"/>
<field name="attachment_ids" eval="[(4, ref('mass_mail_attach_1'))]"/> <field name="attachment_ids" eval="[(4, ref('mass_mail_attach_1'))]"/>
<field name="body_html"><![CDATA[<div data-snippet-id="big-picture" style="padding:0px; margin:0px"> <field name="body_html"><![CDATA[<div data-snippet-id="big-picture" style="padding:0px; margin:0px">
@ -69,29 +68,26 @@
<!-- Create mailing lists --> <!-- Create mailing lists -->
<record id="mass_mail_list_1" model="mail.mass_mailing.list"> <record id="mass_mail_list_1" model="mail.mass_mailing.list">
<field name="name">Imported Contacts</field> <field name="name">Imported Contacts</field>
<field name="model">mail.mass_mailing.contact</field>
</record> </record>
<record id="mass_mail_list_2" model="mail.mass_mailing.list"> <record id="mass_mail_list_2" model="mail.mass_mailing.list">
<field name="name">Customers</field> <field name="name">Customers</field>
<field name="model">res.partner</field>
<field name="domain">[('customer', '=', True)]</field>
</record> </record>
<!-- Create Contacts --> <!-- Create Contacts -->
<record id="mass_mail_contact_1" model="mail.mass_mailing.contact"> <record id="mass_mail_contact_1" model="mail.mass_mailing.contact">
<field name="name">Aristide Antario</field> <field name="name">Aristide Antario</field>
<field name="email">aa@example.com</field> <field name="email">aa@example.com</field>
<field name="list_id" eval="ref('mass_mail_list_1')"/> <field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
</record> </record>
<record id="mass_mail_contact_2" model="mail.mass_mailing.contact"> <record id="mass_mail_contact_2" model="mail.mass_mailing.contact">
<field name="name">Beverly Bridge</field> <field name="name">Beverly Bridge</field>
<field name="email">bb@example.com</field> <field name="email">bb@example.com</field>
<field name="list_id" eval="ref('mass_mail_list_1')"/> <field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
</record> </record>
<record id="mass_mail_contact_3" model="mail.mass_mailing.contact"> <record id="mass_mail_contact_3" model="mail.mass_mailing.contact">
<field name="name">Carol Cartridge</field> <field name="name">Carol Cartridge</field>
<field name="email">cc@example.com</field> <field name="email">cc@example.com</field>
<field name="list_id" eval="ref('mass_mail_list_1')"/> <field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
<field name="opt_out" eval="True"/> <field name="opt_out" eval="True"/>
</record> </record>
@ -103,13 +99,12 @@
<field name="name">Newsletter</field> <field name="name">Newsletter</field>
<field name="stage_id" ref="mass_mailing.campaign_stage_1"/> <field name="stage_id" ref="mass_mailing.campaign_stage_1"/>
<field name="user_id" eval="ref('base.user_root')"/> <field name="user_id" eval="ref('base.user_root')"/>
<field name="category_id" eval="ref('mass_mailing.mass_mail_category_1')"/> <field name="category_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_category_1')])]"/>
</record> </record>
<record id="mass_mail_1" model="mail.mass_mailing"> <record id="mass_mail_1" model="mail.mass_mailing">
<field name="name">First Newsletter</field> <field name="name">First Newsletter</field>
<field name="state">done</field> <field name="state">done</field>
<field name="template_id" eval="ref('mass_mail_template_1')"/>
<field name="date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/> <field name="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="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
<field name="contact_list_ids" eval="[(4, ref('mass_mail_list_2'))]"/> <field name="contact_list_ids" eval="[(4, ref('mass_mail_list_2'))]"/>
@ -117,7 +112,6 @@
<record id="mass_mail_2" model="mail.mass_mailing"> <record id="mass_mail_2" model="mail.mass_mailing">
<field name="name">Second Newsletter</field> <field name="name">Second Newsletter</field>
<field name="state">test</field> <field name="state">test</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="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')"/> <field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
</record> </record>

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import mass_mailing import mass_mailing
import mass_mailing_stats
import mail_mail import mail_mail
import mail_thread import mail_thread
import email_template
import res_config import res_config
import mass_mailing_report import mass_mailing_report

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
from openerp.tools.translate import _
from openerp.osv import osv, fields
class EmailTemplate(osv.Model):
"""Add the mass mailing campaign data to mail"""
_name = 'email.template'
_inherit = ['email.template']
_columns = {
'use_in_mass_mailing': fields.boolean('Available for marketing and mailing'),
}
def action_new_mailing(self, cr, uid, ids, context=None):
template = self.browse(cr, uid, ids[0], context=context)
ctx = dict(context)
ctx.update({
'default_mailing_model': template.model,
'default_template_id': ids[0],
})
return {
'name': _('Create a Mass Mailing'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.mass_mailing',
'views': [(False, 'form')],
'context': ctx,
}
class email_template_preview(osv.TransientModel):
""" Reinitialize email template preview model to have access to all email.template
new fields. """
_name = "email_template.preview"
_inherit = ['email.template', 'email_template.preview']

View File

@ -22,10 +22,7 @@
from datetime import datetime from datetime import datetime
from dateutil import relativedelta from dateutil import relativedelta
import random import random
try: import json
import simplejson as json
except ImportError:
import json
import urllib import urllib
import urlparse import urlparse
@ -40,6 +37,7 @@ class MassMailingCategory(osv.Model):
"""Model of categories of mass mailing, i.e. marketing, newsletter, ... """ """Model of categories of mass mailing, i.e. marketing, newsletter, ... """
_name = 'mail.mass_mailing.category' _name = 'mail.mass_mailing.category'
_description = 'Mass Mailing Category' _description = 'Mass Mailing Category'
_order = 'name'
_columns = { _columns = {
'name': fields.char('Name', required=True), 'name': fields.char('Name', required=True),
@ -50,19 +48,29 @@ class MassMailingContact(osv.Model):
"""Model of a contact. This model is different from the partner 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 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 be able to deal with large contact list to email without bloating the partner
database. """ base."""
_name = 'mail.mass_mailing.contact' _name = 'mail.mass_mailing.contact'
_description = 'Mass Mailing Contact' _description = 'Mass Mailing Contact'
_order = 'email'
_rec_name = 'email'
_columns = { _columns = {
'name': fields.char('Name', required=True), 'name': fields.char('Name'),
'email': fields.char('Email', required=True), 'email': fields.char('Email', required=True),
'create_date': fields.datetime('Create Date'),
'list_id': fields.many2one( 'list_id': fields.many2one(
'mail.mass_mailing.list', string='Mailing List', 'mail.mass_mailing.list', string='Mailing List',
domain=[('model', '=', 'mail.mass_mailing.contact')], ondelete='cascade', required=True,
ondelete='cascade',
), ),
'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive news anymore from this mailing list'), '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): def name_create(self, cr, uid, name, context=None):
@ -78,150 +86,33 @@ class MassMailingContact(osv.Model):
class MassMailingList(osv.Model): class MassMailingList(osv.Model):
"""Model of a contact list. """ """Model of a contact list. """
_name = 'mail.mass_mailing.list' _name = 'mail.mass_mailing.list'
_description = 'Contact List' _order = 'name'
_description = 'Mailing List'
def default_get(self, cr, uid, fields, context=None):
"""Override default_get to handle active_domain coming from the list view. """
res = super(MassMailingList, self).default_get(cr, uid, fields, context=context)
if 'domain' in fields:
if not 'model' in res and context.get('active_model'):
res['model'] = context['active_model']
if 'active_domain' in context:
res['domain'] = '%s' % context['active_domain']
elif 'active_ids' in context:
res['domain'] = '%s' % [('id', 'in', context['active_ids'])]
else:
res['domain'] = False
return res
def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None): def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
"""Compute the number of contacts linked to the mailing list. """ result = dict.fromkeys(ids, 0)
results = dict.fromkeys(ids, 0) Contacts = self.pool.get('mail.mass_mailing.contact')
for contact_list in self.browse(cr, uid, ids, context=context): for group in Contacts.read_group(cr, uid, [('list_id', 'in', ids), ('opt_out', '!=', True)], ['list_id'], ['list_id'], context=context):
results[contact_list.id] = self.pool[contact_list.model].search( result[group['list_id'][0]] = group['list_id_count']
cr, uid, return result
self._get_domain(cr, uid, [contact_list.id], context=context)[contact_list.id],
count=True, context=context
)
return results
def _get_model_list(self, cr, uid, context=None):
return self.pool['mail.mass_mailing']._get_mailing_model(cr, uid, context=context)
# indirections for inheritance
_model_list = lambda self, *args, **kwargs: self._get_model_list(*args, **kwargs)
_columns = { _columns = {
'name': fields.char('Name', required=True), 'name': fields.char('Mailing List', required=True),
'contact_nbr': fields.function( 'contact_nbr': fields.function(
_get_contact_nbr, type='integer', _get_contact_nbr, type='integer',
string='Contact Number', string='Number of Contacts',
), ),
'model': fields.selection(
_model_list, type='char', required=True,
string='Applies To'
),
'filter_id': fields.many2one(
'ir.filters', string='Custom Filter',
domain="[('model_id.model', '=', model)]",
),
'domain': fields.text('Domain'),
} }
def on_change_model(self, cr, uid, ids, model, context=None):
return {'value': {'filter_id': False}}
def on_change_filter_id(self, cr, uid, ids, filter_id, context=None):
values = {}
if filter_id:
ir_filter = self.pool['ir.filters'].browse(cr, uid, filter_id, context=context)
values['domain'] = ir_filter.domain
return {'value': values}
def on_change_domain(self, cr, uid, ids, domain, model, context=None):
if domain is False:
return {'value': {'contact_nbr': 0}}
else:
domain = eval(domain)
return {'value': {'contact_nbr': self.pool[model].search(cr, uid, domain, context=context, count=True)}}
def create(self, cr, uid, values, context=None):
new_id = super(MassMailingList, self).create(cr, uid, values, context=context)
if values.get('model') == 'mail.mass_mailing.contact' and (not context or not context.get('no_contact_to_list')):
domain = values.get('domain')
if domain is None or domain is False:
return new_id
contact_ids = self.pool['mail.mass_mailing.contact'].search(cr, uid, eval(domain), context=context)
self.pool['mail.mass_mailing.contact'].write(cr, uid, contact_ids, {'list_id': new_id}, context=context)
self.pool['mail.mass_mailing.list'].write(cr, uid, [new_id], {'domain': [('list_id', '=', new_id)]}, context=context)
return new_id
def action_see_records(self, cr, uid, ids, context=None):
contact_list = self.browse(cr, uid, ids[0], context=context)
ctx = dict(context)
ctx['search_default_not_opt_out'] = True
return {
'name': _('See Contact List'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': contact_list.model,
'views': [(False, 'tree'), (False, 'form')],
'view_id': False,
'target': 'current',
'context': ctx,
'domain': contact_list.domain,
}
def action_add_to_mailing(self, cr, uid, ids, context=None):
mass_mailing_id = context.get('default_mass_mailing_id')
if not mass_mailing_id:
return False
self.pool['mail.mass_mailing'].write(cr, uid, [mass_mailing_id], {'contact_list_ids': [(4, list_id) for list_id in ids]}, context=context)
return {
'name': _('Mass Mailing'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.mass_mailing',
'res_id': mass_mailing_id,
'context': context,
}
def _get_domain(self, cr, uid, ids, context=None):
domains = {}
for contact_list in self.browse(cr, uid, ids, context=context):
if contact_list.domain is False or contact_list.domain is None: # domain is a string like False or None -> void list
domain = [('id', '=', '0')]
else:
domain = eval(contact_list.domain)
domains[contact_list.id] = domain
return domains
def get_global_domain(self, cr, uid, ids, context=None):
model_to_domains = dict((mailing_model[0], list())
for mailing_model in self.pool['mail.mass_mailing']._get_mailing_model(cr, uid, context=context))
for contact_list in self.browse(cr, uid, ids, context=context):
domain = self._get_domain(cr, uid, [contact_list.id], context=context)[contact_list.id]
if domain is not False:
model_to_domains[contact_list.model].append(domain)
for model, domains in model_to_domains.iteritems():
if domains:
final_domain = ['|'] * (len(domains) - 1) + [leaf for dom in domains for leaf in dom]
else:
final_domain = [('id', '=', '0')]
model_to_domains[model] = final_domain
return model_to_domains
class MassMailingStage(osv.Model): class MassMailingStage(osv.Model):
"""Stage for mass mailing campaigns. """ """Stage for mass mailing campaigns. """
_name = 'mail.mass_mailing.stage' _name = 'mail.mass_mailing.stage'
_description = 'Mass Mailing Campaign Stage' _description = 'Mass Mailing Campaign Stage'
_order = 'sequence ASC' _order = 'sequence'
_columns = { _columns = {
'name': fields.char('Name', required=True), 'name': fields.char('Name', required=True, translate=True),
'sequence': fields.integer('Sequence'), 'sequence': fields.integer('Sequence'),
} }
@ -264,9 +155,9 @@ class MassMailingCampaign(osv.Model):
'res.users', 'Responsible', 'res.users', 'Responsible',
required=True, required=True,
), ),
'category_id': fields.many2one( 'category_ids': fields.many2many(
'mail.mass_mailing.category', 'Category', 'mail.mass_mailing.category', 'mail_mass_mailing_category_rel',
help='Category'), 'category_id', 'campaign_id', string='Categories'),
'mass_mailing_ids': fields.one2many( 'mass_mailing_ids': fields.one2many(
'mail.mass_mailing', 'mass_mailing_campaign_id', 'mail.mass_mailing', 'mass_mailing_campaign_id',
'Mass Mailings', 'Mass Mailings',
@ -326,32 +217,13 @@ class MassMailingCampaign(osv.Model):
def _get_default_stage_id(self, cr, uid, context=None): 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) stage_ids = self.pool['mail.mass_mailing.stage'].search(cr, uid, [], limit=1, context=context)
return stage_ids and stage_ids[0] return stage_ids and stage_ids[0] or False
_defaults = { _defaults = {
'user_id': lambda self, cr, uid, ctx=None: uid, 'user_id': lambda self, cr, uid, ctx=None: uid,
'stage_id': lambda self, cr, uid, ctx=None: self._get_default_stage_id(cr, uid, context=ctx), 'stage_id': lambda self, *args: self._get_default_stage_id(*args),
} }
#------------------------------------------------------
# Actions
#------------------------------------------------------
def action_new_mailing(self, cr, uid, ids, context=None):
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',
'views': [(False, 'form')],
'context': dict(context, default_mass_mailing_campaign_id=ids[0]),
}
#------------------------------------------------------
# API
#------------------------------------------------------
def get_recipients(self, cr, uid, ids, model=None, context=None): def get_recipients(self, cr, uid, ids, model=None, context=None):
"""Return the recipints of a mailing campaign. This is based on the statistics """Return the recipints of a mailing campaign. This is based on the statistics
build for each mailing. """ build for each mailing. """
@ -448,15 +320,14 @@ class MassMailing(osv.Model):
def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None): def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
res = dict.fromkeys(ids, False) res = dict.fromkeys(ids, False)
for mailing in self.browse(cr, uid, ids, context=context): for mailing in self.browse(cr, uid, ids, context=context):
val = {'contact_nbr': 0, 'contact_ab_nbr': 0, 'contact_ab_done': 0} val = {'contact_nbr': 0, 'contact_ab_nbr': 0} # 'contact_ab_done': 0
val['contact_nbr'] = self.pool[mailing.mailing_model].search( val['contact_nbr'] = self.pool[mailing.mailing_model].search(
cr, uid, cr, uid, eval(mailing.mailing_domain),
self.pool['mail.mass_mailing.list'].get_global_domain(cr, uid, [c.id for c in mailing.contact_list_ids], context=context)[mailing.mailing_model],
count=True, context=context count=True, context=context
) )
val['contact_ab_nbr'] = int(val['contact_nbr'] * mailing.contact_ab_pc / 100.0) # val['contact_ab_nbr'] = int(val['contact_nbr'] * mailing.contact_ab_pc / 100.0)
if mailing.mass_mailing_campaign_id and mailing.ab_testing: # if mailing.mass_mailing_campaign_id and mailing.ab_testing:
val['contact_ab_done'] = len(self.pool['mail.mass_mailing.campaign'].get_recipients(cr, uid, [mailing.mass_mailing_campaign_id.id], context=context)[mailing.mass_mailing_campaign_id.id]) # val['contact_ab_done'] = len(self.pool['mail.mass_mailing.campaign'].get_recipients(cr, uid, [mailing.mass_mailing_campaign_id.id], context=context)[mailing.mass_mailing_campaign_id.id])
res[mailing.id] = val res[mailing.id] = val
return res return res
@ -471,42 +342,32 @@ class MassMailing(osv.Model):
def _get_mailing_model(self, cr, uid, context=None): def _get_mailing_model(self, cr, uid, context=None):
return [ return [
('res.partner', 'Customers'), ('res.partner', _('Customers')),
('mail.mass_mailing.contact', 'Contacts') ('mail.mass_mailing.contact', _('Mailing List'))
] ]
def _get_state_list(self, cr, uid, context=None):
return [('draft', 'Schedule'), ('test', 'Tested'), ('done', 'Sent')]
# indirections for inheritance # indirections for inheritance
_mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(*args, **kwargs) _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(*args, **kwargs)
_state = lambda self, *args, **kwargs: self._get_state_list(*args, **kwargs)
_columns = { _columns = {
'name': fields.char('Subject', required=True), 'name': fields.char('Subject', required=True),
'email_from': fields.char('From', required=True),
'date': fields.datetime('Date'), 'date': fields.datetime('Date'),
'state': fields.selection(
_state, string='Status', required=True,
),
'template_id': fields.many2one(
'email.template', 'Email Template',
domain="[('use_in_mass_mailing', '=', True), ('model', '=', mailing_model)]",
),
'body_html': fields.html('Body'), 'body_html': fields.html('Body'),
'mass_mailing_campaign_id': fields.many2one( 'mass_mailing_campaign_id': fields.many2one(
'mail.mass_mailing.campaign', 'Mass Mailing Campaign', 'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
ondelete='set null', ondelete='set null',
), ),
'ab_testing': fields.related( 'state': fields.selection(
'mass_mailing_campaign_id', 'ab_testing', [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')],
type='boolean', string='AB Testing' string='Status', required=True,
), ),
'color': fields.related( 'color': fields.related(
'mass_mailing_campaign_id', 'color', 'mass_mailing_campaign_id', 'color',
type='integer', string='Color Index', type='integer', string='Color Index',
), ),
# mailing options # mailing options
'email_from': fields.char('From'), # TODO: simplify these 4 fields
'reply_in_thread': fields.boolean('Reply in thread'), 'reply_in_thread': fields.boolean('Reply in thread'),
'reply_specified': fields.boolean('Specific Reply-To'), 'reply_specified': fields.boolean('Specific Reply-To'),
'auto_reply_to_available': fields.function( 'auto_reply_to_available': fields.function(
@ -514,11 +375,12 @@ class MassMailing(osv.Model):
type='boolean', string='Reply in thread available' type='boolean', string='Reply in thread available'
), ),
'reply_to': fields.char('Reply To'), 'reply_to': fields.char('Reply To'),
'mailing_model': fields.selection(_mailing_model, string='Type', required=True), # Target Emails
'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True),
'mailing_domain': fields.char('Domain'),
'contact_list_ids': fields.many2many( 'contact_list_ids': fields.many2many(
'mail.mass_mailing.list', 'mail_mass_mailing_list_rel', 'mail.mass_mailing.list', 'mail_mass_mailing_list_rel',
string='Mailing Lists', string='Mailing Lists',
domain="[('model', '=', mailing_model)]",
), ),
'contact_nbr': fields.function( 'contact_nbr': fields.function(
_get_contact_nbr, type='integer', multi='_get_contact_nbr', _get_contact_nbr, type='integer', multi='_get_contact_nbr',
@ -528,14 +390,6 @@ class MassMailing(osv.Model):
'AB Testing percentage', 'AB Testing percentage',
help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.' help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.'
), ),
'contact_ab_nbr': fields.function(
_get_contact_nbr, type='integer', multi='_get_contact_nbr',
string='Contact Number in AB Testing'
),
'contact_ab_done': fields.function(
_get_contact_nbr, type='integer', multi='_get_contact_nbr',
string='Number of already mailed contacts'
),
# statistics data # statistics data
'statistics_ids': fields.one2many( 'statistics_ids': fields.one2many(
'mail.mail.statistics', 'mass_mailing_id', 'mail.mail.statistics', 'mass_mailing_id',
@ -602,7 +456,7 @@ class MassMailing(osv.Model):
'state': 'draft', 'state': 'draft',
'date': fields.datetime.now, 'date': fields.datetime.now,
'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx), 'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
'mailing_model': 'res.partner', 'mailing_model': 'mail.mass_mailing.contact',
'contact_ab_pc': 100, 'contact_ab_pc': 100,
} }
@ -617,7 +471,6 @@ class MassMailing(osv.Model):
default.update({ default.update({
'state': 'draft', 'state': 'draft',
'statistics_ids': [], 'statistics_ids': [],
'state': 'draft',
'name': _('%s (duplicate)') % mailing.name, 'name': _('%s (duplicate)') % mailing.name,
}) })
return super(MassMailing, self).copy_data(cr, uid, id, default, context=context) return super(MassMailing, self).copy_data(cr, uid, id, default, context=context)
@ -626,7 +479,8 @@ class MassMailing(osv.Model):
""" Override read_group to always display all states. """ """ Override read_group to always display all states. """
if groupby and groupby[0] == "state": if groupby and groupby[0] == "state":
# Default result structure # Default result structure
states = self._get_state_list(cr, uid, context=context) # states = self._get_state_list(cr, uid, context=context)
states = [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')]
read_group_all_states = [{ read_group_all_states = [{
'__context': {'group_by': groupby[1:]}, '__context': {'group_by': groupby[1:]},
'__domain': domain + [('state', '=', state_value)], '__domain': domain + [('state', '=', state_value)],
@ -651,16 +505,20 @@ class MassMailing(osv.Model):
# Views & Actions # Views & Actions
#------------------------------------------------------ #------------------------------------------------------
def on_change_mailing_model(self, cr, uid, ids, mailing_model, context=None): def on_change_model(self, cr, uid, ids, mailing_model, list_ids, context=None):
values = { value = {}
'contact_list_ids': [], if mailing_model=='mail.mass_mailing.contact':
'template_id': False, if list_ids and list_ids[0][0]==6 and list_ids[0][2]:
'contact_nbr': 0, value['mailing_domain'] = "[('list_id', 'in', ["+','.join(map(str, list_ids[0][2]))+"])]"
'auto_reply_to_available': not mailing_model in self._get_private_models(context), else:
'reply_in_thread': not mailing_model in self._get_private_models(context), value['mailing_domain'] = "[('list_id', '=', False)]"
'reply_specified': mailing_model in self._get_private_models(context) value['contact_nbr'] = self.pool[mailing_model].search(
} cr, uid, eval(value['mailing_domain']), count=True, context=context
return {'value': values} )
else:
value['mailing_domain'] = False
value['contact_nbr'] = 0
return {'value': value}
def on_change_reply_specified(self, cr, uid, ids, reply_specified, reply_in_thread, context=None): def on_change_reply_specified(self, cr, uid, ids, reply_specified, reply_in_thread, context=None):
if reply_specified == reply_in_thread: if reply_specified == reply_in_thread:
@ -680,30 +538,13 @@ class MassMailing(osv.Model):
list_ids += command[2] list_ids += command[2]
if list_ids: if list_ids:
values['contact_nbr'] = self.pool[mailing_model].search( values['contact_nbr'] = self.pool[mailing_model].search(
cr, uid, cr, uid, [('list_id', 'in', list_ids), ('opt_out','!=',1)],
self.pool['mail.mass_mailing.list'].get_global_domain(cr, uid, list_ids, context=context)[mailing_model],
count=True, context=context count=True, context=context
) )
return {'value': values}
def on_change_template_id(self, cr, uid, ids, template_id, context=None):
values = {}
if template_id:
template = self.pool['email.template'].browse(cr, uid, template_id, context=context)
if template.email_from:
values['email_from'] = template.email_from
if template.reply_to:
values['reply_to'] = template.reply_to
values['body_html'] = template.body_html
else: else:
values['email_from'] = self.pool['mail.message']._get_default_from(cr, uid, context=context) values['contact_nbr'] = 0
values['reply_to'] = False
values['body_html'] = False
return {'value': values} return {'value': values}
def on_change_contact_ab_pc(self, cr, uid, ids, contact_ab_pc, contact_nbr, context=None):
return {'value': {'contact_ab_nbr': contact_nbr * contact_ab_pc / 100.0}}
def action_duplicate(self, cr, uid, ids, context=None): def action_duplicate(self, cr, uid, ids, context=None):
copy_id = None copy_id = None
for mid in ids: for mid in ids:
@ -719,27 +560,14 @@ class MassMailing(osv.Model):
} }
return False return False
def _get_model_to_list_action_id(self, cr, uid, model, context=None): def action_test_mailing(self, cr, uid, ids, context=None):
if model == 'res.partner': ctx = dict(context, default_mass_mailing_id=ids[0])
return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_partner_to_mailing_list')
else:
return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_contact_to_mailing_list')
def action_new_list(self, cr, uid, ids, context=None):
mailing = self.browse(cr, uid, ids[0], context=context)
action_id = self._get_model_to_list_action_id(cr, uid, mailing.mailing_model, context=context)
ctx = dict(context,
search_default_not_opt_out=True,
view_manager_highlight=[action_id],
default_name=mailing.name,
default_mass_mailing_id=ids[0],
default_model=mailing.mailing_model)
return { return {
'name': _('Choose Recipients'), 'name': _('Test Mailing'),
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'view_type': 'form', 'view_mode': 'form',
'view_mode': 'tree,form', 'res_model': 'mail.mass_mailing.test',
'res_model': mailing.mailing_model, 'target': 'new',
'context': ctx, 'context': ctx,
} }
@ -757,19 +585,11 @@ class MassMailing(osv.Model):
'context': context, 'context': context,
} }
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): def action_edit_html(self, cr, uid, ids, context=None):
url = '/website_mail/email_designer?model=mail.mass_mailing&res_id=%d' % ids[0] # fixme: assert is not correct
assert len(ids)==1, "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&field_body=body_html&field_from=email_form&field_subject=name&template_model=%s' % (ids[0], mail.mailing_model)
return { return {
'name': _('Open with Visual Editor'), 'name': _('Open with Visual Editor'),
'type': 'ir.actions.act_url', 'type': 'ir.actions.act_url',
@ -791,13 +611,14 @@ class MassMailing(osv.Model):
return dict((partner.id, {'partner_id': partner.id, 'name': partner.name, 'email': partner.email}) for partner in partners) return dict((partner.id, {'partner_id': partner.id, 'name': partner.name, 'email': partner.email}) for partner in partners)
def get_recipients(self, cr, uid, mailing, context=None): def get_recipients(self, cr, uid, mailing, context=None):
domain = self.pool['mail.mass_mailing.list'].get_global_domain( domain = eval(mailing.mailing_domain)
cr, uid, [l.id for l in mailing.contact_list_ids], context=context # self.pool['mail.mass_mailing.list'].get_global_domain(
)[mailing.mailing_model] # cr, uid, [l.id for l in mailing.contact_list_ids], context=context
# )[mailing.mailing_model]
res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context) res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context)
# randomly choose a fragment # randomly choose a fragment
if mailing.contact_ab_pc != 100: if mailing.contact_ab_pc < 100:
topick = mailing.contact_ab_nbr topick = mailing.contact_ab_nbr
if mailing.mass_mailing_campaign_id and mailing.ab_testing: if mailing.mass_mailing_campaign_id and mailing.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] 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]
@ -846,80 +667,3 @@ class MassMailing(osv.Model):
return True return True
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
'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

View File

@ -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

View File

@ -1,4 +1,5 @@
openerp.mass_mailing = function(openerp) { openerp.mass_mailing = function (instance) {
var _t = instance.web._t;
openerp.web_kanban.KanbanRecord.include({ openerp.web_kanban.KanbanRecord.include({
on_card_clicked: function (event) { on_card_clicked: function (event) {
@ -10,4 +11,65 @@ openerp.mass_mailing = function(openerp) {
}, },
}); });
instance.web.form.CharDomainButton = instance.web.form.AbstractField.extend({
template: 'CharDomainButton',
init: function(field_manager, node) {
this._super.apply(this, arguments);
},
start: function() {
var self=this;
this._super.apply(this, arguments);
$('button', this.$el).on('click', self.on_click);
this.set_button();
},
set_button: function() {
var self = this;
// debugger
if (this.get('value')) {
// TODO: rpc to copute X
var domain = instance.web.pyeval.eval('domain', this.get('value'));
var relation = this.getParent().fields.mailing_model.get('value')[0];
var ds = new instance.web.DataSetStatic(self, relation, self.build_context());
ds.call('search_count', [domain]).then(function (results) {
$('.oe_domain_count', self.$el).text(results + ' records selected');
$('button span', self.$el).text(' Change selection');
});
} else {
$('.oe_domain_count', this.$el).text('0 record selected');
$('button span', this.$el).text(' Select records');
};
},
on_click: function(ev) {
var self = this;
var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
this.pop = new instance.web.form.SelectCreatePopup(this);
this.pop.select_element(
model, {title: 'Select records...'},
[], this.build_context());
this.pop.on("elements_selected", self, function() {
var self2 = this;
var search_data = this.pop.searchview.build_search_data()
instance.web.pyeval.eval_domains_and_contexts({
domains: search_data.domains,
contexts: search_data.contexts,
group_by_seq: search_data.groupbys || []
}).then(function (results) {
// if selected IDS change domain
var domain = self2.pop.dataset.domain.concat(results.domain || []);
self.set_value(JSON.stringify(domain))
});
});
event.preventDefault();
},
set_value: function(value_) {
var self = this;
this.set('value', value_ || false);
this.set_button();
},
});
instance.web.form.widgets = instance.web.form.widgets.extend(
{'char_domain': 'instance.web.form.CharDomainButton'}
);
}; };

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<t t-name="CharDomainButton">
<div class="oe_form_field">
<span class="oe_domain_count"/>
<button class="oe_button oe_link" type="button"
t-att-style="widget.node.attrs.style"
t-att-accesskey="widget.node.attrs.accesskey">
<span class="fa fa-arrow-right"/>
</button>
</div>
</t>
</template>

View File

@ -3,17 +3,6 @@
<data> <data>
<!-- Email Templates --> <!-- Email Templates -->
<record id="email_template_form_mass_mailing" model="ir.ui.view">
<field name="name">email.template.form.mass.mailing</field>
<field name="model">email.template</field>
<field name="inherit_id" ref="email_template.email_template_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='model_id']" position="after">
<field name="use_in_mass_mailing"/>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="email_template_form_minimal"> <record model="ir.ui.view" id="email_template_form_minimal">
<field name="name">email.template.form.minimal</field> <field name="name">email.template.form.minimal</field>
<field name="model">email.template</field> <field name="model">email.template</field>
@ -29,7 +18,6 @@
domain="[('model', 'in', ['res.partner', 'mail.mass_mailing.contact'])]"/> domain="[('model', 'in', ['res.partner', 'mail.mass_mailing.contact'])]"/>
<field name="model" invisible="True"/> <field name="model" invisible="True"/>
<field name="use_default_to" invisible="1"/> <field name="use_default_to" invisible="1"/>
<field name="use_in_mass_mailing" invisible="1"/>
</group> </group>
<group> <group>
<div class="oe_right oe_button_box" name="buttons"> <div class="oe_right oe_button_box" name="buttons">
@ -37,8 +25,8 @@
type="action" target="new" type="action" target="new"
context="{'template_id':active_id}"/> context="{'template_id':active_id}"/>
<br /> <br />
<field name="website_link" widget='html' radonly='1' <!-- <field name="website_link" widget='html' radonly='1'
style='margin: 0px; padding: 0px;'/> style='margin: 0px; padding: 0px;'/> -->
</div> </div>
</group> </group>
</group> </group>
@ -53,19 +41,6 @@
</field> </field>
</record> </record>
<record id="view_email_template_search_mass_mailing" model="ir.ui.view">
<field name="name">email.template.search.mass.mailing</field>
<field name="model">email.template</field>
<field name="inherit_id" ref="email_template.view_email_template_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='model_id']" position="after">
<separator/>
<filter string="Available for Marketing and Mailing" name="use_in_mass_mailing" domain="[('use_in_mass_mailing', '=', 1)]"
help="Available for use in mass mailings"/>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="view_email_template_kanban"> <record model="ir.ui.view" id="view_email_template_kanban">
<field name="name">email.template.kanban</field> <field name="name">email.template.kanban</field>
<field name="model">email.template</field> <field name="model">email.template</field>
@ -78,9 +53,6 @@
<div class="oe_dropdown_toggle oe_dropdown_kanban"> <div class="oe_dropdown_toggle oe_dropdown_kanban">
<span class="oe_e">i</span> <span class="oe_e">i</span>
<ul class="oe_dropdown_menu"> <ul class="oe_dropdown_menu">
<t>
<li><a name="action_new_mailing" type="object">New Mailing</a></li>
</t>
<t t-if="widget.view.is_action_enabled('edit')"> <t t-if="widget.view.is_action_enabled('edit')">
<li><a type="edit">Edit</a></li> <li><a type="edit">Edit</a></li>
</t> </t>
@ -110,11 +82,9 @@
<field name="res_model">email.template</field> <field name="res_model">email.template</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field> <field name="view_mode">kanban,tree,form</field>
<field name="domain" eval="[('use_in_mass_mailing', '=', True)]"/>
<field name="context">{ <field name="context">{
'form_view_ref': 'mass_mailing.email_template_form_minimal', 'form_view_ref': 'mass_mailing.email_template_form_minimal',
'default_use_default_to': True, 'default_use_default_to': True,
'default_use_in_mass_mailing': True,
}</field> }</field>
</record> </record>
@ -124,4 +94,4 @@
action="action_email_template_marketing"/> action="action_email_template_marketing"/>
</data> </data>
</openerp> </openerp>

View File

@ -6,25 +6,26 @@
<menuitem name="Mass Mailing" id="mass_mailing_campaign" <menuitem name="Mass Mailing" id="mass_mailing_campaign"
parent="base.marketing_menu" sequence="1"/> parent="base.marketing_menu" sequence="1"/>
<!-- Marketing / Mailing Lists --> <!-- Marketing / Mailing Lists -->
<menuitem name="Contact Lists" id="mass_mailing_list" <menuitem name="Mailing Lists" id="mass_mailing_list"
parent="base.marketing_menu" sequence="2"/> parent="base.marketing_menu" sequence="2"/>
<!-- Marketing / Configuration --> <!-- Marketing / Configuration -->
<menuitem name="Configuration" id="marketing_configuration" <menuitem name="Configuration" id="marketing_configuration"
parent="base.marketing_menu" sequence="99"/> parent="base.marketing_menu" sequence="99"/>
<!-- MASS MAILING CONTACT !--> <!-- MASS MAILING CONTACT -->
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_search"> <record model="ir.ui.view" id="view_mail_mass_mailing_contact_search">
<field name="name">mail.mass_mailing.contact.search</field> <field name="name">mail.mass_mailing.contact.search</field>
<field name="model">mail.mass_mailing.contact</field> <field name="model">mail.mass_mailing.contact</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Mass Mailings"> <search string="Mailing Lists Subscribers">
<field name="name"/> <field name="name"/>
<field name="email"/> <field name="email"/>
<field name="list_id"/> <field name="list_id"/>
<separator/> <separator/>
<filter string="Available for Mass Mailing" name="not_opt_out" domain="[('opt_out', '=', False)]" <filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"/>
help="Contact is not opt-out"/>
<group expand="0" string="Group By..."> <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" <filter string="Mailing Lists" name="group_list_id"
context="{'group_by': 'list_id'}"/> context="{'group_by': 'list_id'}"/>
</group> </group>
@ -37,67 +38,48 @@
<field name="model">mail.mass_mailing.contact</field> <field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field> <field name="priority">10</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Mass Mailings"> <tree string="Mailing Lists Subscribers" editable="top">
<field name="name"/>
<field name="email"/> <field name="email"/>
<field name="name"/>
<field name="list_id"/> <field name="list_id"/>
<field name="opt_out"/> <field name="opt_out"/>
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_form">
<field name="name">mail.mass_mailing.contact.form</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="arch" type="xml">
<form string="Mass Mailing" version="7.0">
<sheet>
<group>
<field name="name"/>
<field name="email"/>
<field name="list_id"/>
<field name="opt_out"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_view_mass_mailing_contacts" model="ir.actions.act_window"> <record id="action_view_mass_mailing_contacts" model="ir.actions.act_window">
<field name="name">Mass Mailing Contacts</field> <field name="name">Mailing List Subscribers</field>
<field name="res_model">mail.mass_mailing.contact</field> <field name="res_model">mail.mass_mailing.contact</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree</field>
<field name="context">{'search_default_not_opt_out': 1}</field> <field name="context">{'search_default_not_opt_out': 1}</field>
</record> </record>
<menuitem name="Contacts" id="menu_email_mass_mailing_contacts" groups="base.group_no_one" <record id="action_view_mass_mailing_contacts_from_list" model="ir.actions.act_window">
<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" parent="mass_mailing_list" sequence="50"
action="action_view_mass_mailing_contacts"/> action="action_view_mass_mailing_contacts"/>
<!-- Create a Mailing List from Contacts --> <!-- MASS MAILING LIST -->
<act_window name="Create Mailing List"
res_model="mail.mass_mailing.list.confirm"
src_model="mail.mass_mailing.contact"
view_mode="form"
multi="True"
target="new"
key2="client_action_multi"
id="action_contact_to_mailing_list"
context="{
'default_mass_mailing_id': context.get('default_mass_mailing_id'),
'default_model': context.get('default_model', 'mail.mass_mailing.contact'),
'default_name': context.get('default_name', False)}"/>
<!-- MASS MAILING LIST !-->
<record model="ir.ui.view" id="view_mail_mass_mailing_list_search"> <record model="ir.ui.view" id="view_mail_mass_mailing_list_search">
<field name="name">mail.mass_mailing.list.search</field> <field name="name">mail.mass_mailing.list.search</field>
<field name="model">mail.mass_mailing.list</field> <field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Mass Mailings"> <search string="Mailing Lists">
<field name="name"/> <field name="name"/>
<separator/> </search>
</search>
</field> </field>
</record> </record>
@ -106,9 +88,8 @@
<field name="model">mail.mass_mailing.list</field> <field name="model">mail.mass_mailing.list</field>
<field name="priority">10</field> <field name="priority">10</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Contact Lists"> <tree string="Mailing Lists">
<field name="name"/> <field name="name"/>
<field name="model"/>
<field name="contact_nbr"/> <field name="contact_nbr"/>
</tree> </tree>
</field> </field>
@ -119,27 +100,19 @@
<field name="model">mail.mass_mailing.list</field> <field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Contact List" version="7.0"> <form string="Contact List" version="7.0">
<header>
<button name="action_add_to_mailing" type="object"
class="oe_highlight" string="Continue to Mailing"
invisible="not context.get('default_mass_mailing_id')"/>
</header>
<sheet> <sheet>
<group> <div class="oe_right oe_button_box" name="buttons">
<field name="name" string="Mailing List Name"/> <button name="%(mass_mailing.action_view_mass_mailing_contacts_from_list)d"
<label for="contact_nbr"/> type="action" icon="fa-user" class="oe_stat_button pull-right">
<div> <field name="contact_nbr" string="Recipients" widget="statinfo"/>
<field name="contact_nbr" nolabel="1" class="oe_inline"/> </button>
<field name="model" class="oe_inline" </div>
on_change="on_change_model(model, context)" nolabel="1"/> <div class="oe_title">
<button string="See Recipients" class="oe_inline oe_link" style="margin-left: 8px;" <label for="name" class="oe_edit_only"/>
name="action_see_records" type="object"/> <h1>
</div> <field name="name"/>
<field name="filter_id" groups="base.group_no_one" </h1>
on_change="on_change_filter_id(filter_id, context)"/> </div>
<field name="domain" groups="base.group_no_one"
on_change="on_change_domain(domain, model, context)"/>
</group>
</sheet> </sheet>
</form> </form>
</field> </field>
@ -150,15 +123,16 @@
<field name="res_model">mail.mass_mailing.list</field> <field name="res_model">mail.mass_mailing.list</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="help" type="html"> <field name="help" type="html">
<p class="oe_view_nocontent_create"> <p class="oe_view_nocontent_create">
Click here to create a new mailing list. Click here to create a new mailing list.
</p><p> </p><p>
Mailing lists allows you to to manage customers and contacts easily and to send to mailings in a single click. Mailing lists allows you to to manage customers and
contacts easily and to send to mailings in a single click.
</p></field> </p></field>
</record> </record>
<menuitem name="Contact Lists" id="menu_email_mass_mailing_lists" <menuitem name="Mailing Lists" id="menu_email_mass_mailing_lists"
parent="mass_mailing_list" sequence="40" parent="mass_mailing_list" sequence="40"
action="action_view_mass_mailing_lists"/> action="action_view_mass_mailing_lists"/>
@ -170,15 +144,12 @@
<search string="Mass Mailings"> <search string="Mass Mailings">
<field name="name" string="Mailings"/> <field name="name" string="Mailings"/>
<field name="mass_mailing_campaign_id"/> <field name="mass_mailing_campaign_id"/>
<field name="template_id"/>
<group expand="0" string="Group By..."> <group expand="0" string="Group By...">
<filter string="State" name="group_state" <filter string="State" name="group_state"
context="{'group_by': 'state'}"/> context="{'group_by': 'state'}"/>
<filter string="Campaign" name="group_mass_mailing_campaign_id" <filter string="Campaign" name="group_mass_mailing_campaign_id"
groups="mass_mailing.group_mass_mailing_campaign" groups="mass_mailing.group_mass_mailing_campaign"
context="{'group_by': 'mass_mailing_campaign_id'}"/> context="{'group_by': 'mass_mailing_campaign_id'}"/>
<filter string="Template" name="group_template_id"
context="{'group_by': 'template_id'}"/>
</group> </group>
</search> </search>
</field> </field>
@ -197,7 +168,6 @@
<field name="replied"/> <field name="replied"/>
<field name="mass_mailing_campaign_id" <field name="mass_mailing_campaign_id"
groups="mass_mailing.group_mass_mailing_campaign"/> groups="mass_mailing.group_mass_mailing_campaign"/>
<field name="template_id" invisible="1"/>
</tree> </tree>
</field> </field>
</record> </record>
@ -209,127 +179,129 @@
<form string="Mass Mailing" version="7.0"> <form string="Mass Mailing" version="7.0">
<header> <header>
<button name="action_test_mailing" type="object" <button name="action_test_mailing" type="object"
class="oe_highlight" string="Test Mailing"/> class="oe_highlight" string="Test Mailing" states="draft"/>
<button name="send_mail" type="object" <button name="send_mail" type="object" states="draft,test"
class="oe_highlight" string="Send to All"/> class="oe_highlight" string="Send to All"/>
<field name="state" widget="statusbar" clickable="True"/> <button name="action_test_mailing" type="object" states="test,done"
string="Send Test Sample"/>
<field name="state" widget="statusbar"/>
</header> </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> <sheet>
<div colspan="2" class="oe_form_box_info oe_text_center" <div class="oe_button_box pull-right" attrs="{'invisible': [('state', 'in', ('draft','test'))]}">
attrs="{'invisible': [('scheduled', '=', 0)]}"> <button name="%(action_mail_mass_mailing_report)d"
<p> type="action" class="oe_stat_button">
<strong><field name="scheduled" class="oe_inline"/>emails are in queue <field name="received_ratio" string="Received" widget="percentpie"/>
and will be sent soon.</strong> </button>
</p> <button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button">
<field name="opened_ratio" string="Opened" widget="percentpie"/>
</button>
<button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button">
<field name="replied_ratio" string="Replied" widget="percentpie"/>
</button>
</div> </div>
<div class="oe_button_box" attrs="{'invisible': [('state', 'in', ('draft','test'))]}" style="margin-bottom: 32px">
<button name="action_see_recipients" type="object"
icon="fa-user" class="oe_stat_button">
<field name="contact_nbr" string="Recipients" widget="statinfo"/>
</button>
<button name="%(action_mail_mass_mailing_report)d" type="action"
icon="fa-envelope-o" class="oe_stat_button">
<field name="total" string="Emails" widget="statinfo"/>
</button>
</div>
<div class="oe_button_box"
style="margin-top: 8px;"
attrs="{'invisible': [('total', '=', 0)]}">
<button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button oe_inline">
<field name="opened_dayly" string="Opened Daily" widget="barchart"/>
</button>
<button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button oe_inline">
<field name="replied_dayly" string="Replied Daily" widget="barchart"/>
</button>
</div>
<group> <group>
<group> <field name="email_from"/>
<field name="email_from"/> <field name="name"/>
<field name="name"/>
</group>
<group>
<div class="oe_right oe_button_box" name="buttons">
<div>
<button name="action_see_recipients" type="object"
icon="fa-user" class="oe_stat_button">
<field name="contact_nbr" string="Recipients" widget="statinfo"/>
</button>
<button name="%(action_mail_mass_mailing_report)d" type="action"
icon="fa-envelope-o" class="oe_stat_button">
<field name="total" string="Emails" widget="statinfo"/>
</button>
</div>
<div style="margin-top: 8px;"
attrs="{'invisible': [('total', '=', 0)]}">
<button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button">
<field name="received_ratio" string="Received" widget="percentpie"/>
</button>
<button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button">
<field name="opened_ratio" string="Opened" widget="percentpie"/>
</button>
<button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button">
<field name="replied_ratio" string="Replied" widget="percentpie"/>
</button>
</div>
<div style="margin-top: 8px;"
attrs="{'invisible': [('total', '=', 0)]}">
<button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button oe_inline">
<field name="opened_dayly" string="Opened Daily" widget="barchart"/>
</button>
<button name="%(action_mail_mass_mailing_report)d"
type="action" class="oe_stat_button oe_inline">
<field name="replied_dayly" string="Replied Daily" widget="barchart"/>
</button>
</div>
</div>
</group>
</group>
<group>
<label for="reply_to"/>
<div>
<field name="auto_reply_to_available" invisible="1"/>
<field name="reply_in_thread" class="oe_inline"
on_change="on_change_reply_in_thread(reply_specified, reply_in_thread, context)"
attrs="{'readonly': [('auto_reply_to_available', '=', False)]}"/>
<span attrs="{'invisible': [('auto_reply_to_available', '=', False)]}">
Replies go into the original document
</span>
<span class="oe_grey" attrs="{'invisible': [('auto_reply_to_available', '=', True)]}">
Replies go into the original document (not available for those recipients)
</span>
<br />
<field name="reply_specified" class="oe_inline"
on_change="on_change_reply_specified(reply_specified, reply_in_thread, context)"/> Use a specific reply-to address
<field name="reply_to" class="oe_inline"
style="margin-left: 8px;"
attrs="{'required': [('reply_specified', '=', True)]}"/>
</div>
<label for="mailing_model" string="Recipients"/> <label for="mailing_model" string="Recipients"/>
<div> <div>
<field name="mailing_model" widget="radio" <field name="mailing_model" widget="radio" style="margin-bottom: 8px"
on_change='on_change_mailing_model(mailing_model, context)'/> on_change="on_change_model(mailing_model, contact_list_ids)"/>
<label for="contact_list_ids" string="Mailing Lists" style="display: inline-block; min-width: 90px;"/> <field name="mailing_domain" widget="char_domain"
<field name="contact_list_ids" widget="many2many_tags" options="{'no_create': True}" attrs="{'invisible': [('mailing_model', '=', 'mail.mass_mailing.contact')]}"
class="oe_inline" placeholder="Choose mailing lists" placeholder="Select recipients"
on_change="on_change_contact_list_ids(mailing_model, contact_list_ids, context)"/> options="{'model_field': 'mailing_model'}"/>
<span style="margin-left: 8px; margin-right: 8px">or</span>
<button string='Create a New List' class="oe_link" type='object' name='action_new_list'/><br />
<div groups="mass_mailing.group_mass_mailing_campaign" style="display: inline;"> <div attrs="{'invisible': [('mailing_model', '&lt;&gt;', 'mail.mass_mailing.contact')]}">
<field name="ab_testing" invisible="1"/> <label for="contact_list_ids" string="Select mailing lists:" class="oe_edit_only"/>
<label for="contact_ab_pc" string="AB Testing" style="display: inline-block; min-width: 90px;"/> <field name="contact_list_ids" widget="many2many_tags"
Email <field name="contact_ab_pc" class="oe_inline" placeholder="Select mailing lists..." class="oe_inline"
on_change="on_change_contact_ab_pc(contact_ab_pc, contact_nbr, context)"/> on_change="on_change_model(mailing_model, contact_list_ids)"/>
<strong>%</strong> of recipients </div>
(<field name="contact_ab_nbr" class="oe_inline"/> recipients) <div attrs="{'invisible': [('contact_nbr', '&lt;', 1)]}">
<div attrs="{'invisible': [('ab_testing', '=', False)]}" style="display: inline;"> <strong>
<span>(</span> <field name="contact_nbr" class="oe_inline"/> Recipients
<field name="contact_ab_done" class="oe_inline" </strong>
attrs="{'invisible': [('ab_testing', '=', False)]}"/> already mailed
<span>)</span>
</div>
</div> </div>
</div> </div>
<field name="date" readonly="True" groups="mass_mailing.group_mass_mailing_campaign"
attrs="{'invisible': [('state', '!=', 'done')]}"/>
<field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/>
<label for="body_html" string="Email"/>
<div>
<label for="template_id" string="Template"/>
<field name="template_id" string="Select Template"
class="oe_inline" options="{'no_create': True, 'no_open': True}"
on_change="on_change_template_id(template_id, context)"/><br />
<button name="action_edit_html" type="object" string="Edit Mail Content"
class="oe_link" style="margin-left: 8px"/>
<field name="body_html"/>
</div>
</group> </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' : ['|', ('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"/>
</page>
<page string="Options">
<group>
<group string="Campaign" groups="mass_mailing.group_mass_mailing_campaign">
<field name="mass_mailing_campaign_id"/>
<label for="contact_ab_pc"/>
<div>
<field name="contact_ab_pc" class="oe_inline"/> %
</div>
</group><group>
<field name="date"/>
</group><group>
<label for="reply_to"/>
<div>
<field name="auto_reply_to_available" invisible="1"/>
<field name="reply_in_thread" class="oe_inline"
on_change="on_change_reply_in_thread(reply_specified, reply_in_thread, context)"
attrs="{'readonly': [('auto_reply_to_available', '=', False)]}"/>
<span attrs="{'invisible': [('auto_reply_to_available', '=', False)]}">
Replies go into the original document
</span>
<span class="oe_grey" attrs="{'invisible': [('auto_reply_to_available', '=', True)]}">
Replies go into the original document (not available for those recipients)
</span>
<br />
<field name="reply_specified" class="oe_inline"
on_change="on_change_reply_specified(reply_specified, reply_in_thread, context)"/> Use a specific reply-to address
<field name="reply_to" class="oe_inline"
style="margin-left: 8px;"
attrs="{'required': [('reply_specified', '=', True)]}"/>
</div>
</group>
</group>
</page>
</notebook>
</sheet> </sheet>
</form> </form>
</field> </field>
@ -429,28 +401,12 @@
<field name="model">mail.mass_mailing.stage</field> <field name="model">mail.mass_mailing.stage</field>
<field name="priority">10</field> <field name="priority">10</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Mass Mailings"> <tree string="Mass Mailings" editable="top">
<field name="sequence" widget="handle"/>
<field name="name"/> <field name="name"/>
<field name="sequence"/>
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_mail_mass_mailing_stage_form">
<field name="name">mail.mass_mailing.stage.form</field>
<field name="model">mail.mass_mailing.stage</field>
<field name="arch" type="xml">
<form string="Mass Mailing" version="7.0">
<sheet>
<group>
<field name="name"/>
<field name="sequence"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_view_mass_mailing_stages" model="ir.actions.act_window"> <record id="action_view_mass_mailing_stages" model="ir.actions.act_window">
<field name="name">Mass Mailing Stages</field> <field name="name">Mass Mailing Stages</field>
<field name="res_model">mail.mass_mailing.stage</field> <field name="res_model">mail.mass_mailing.stage</field>
@ -460,6 +416,7 @@
<menuitem name="Campaign Stages" id="menu_view_mass_mailing_stages" <menuitem name="Campaign Stages" id="menu_view_mass_mailing_stages"
parent="marketing_configuration" sequence="1" parent="marketing_configuration" sequence="1"
groups="mass_mailing.group_mass_mailing_campaign"
action="action_view_mass_mailing_stages"/> action="action_view_mass_mailing_stages"/>
<!-- MASS MAILING CAMPAIGNS !--> <!-- MASS MAILING CAMPAIGNS !-->
@ -469,15 +426,13 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Mass Mailing Campaigns"> <search string="Mass Mailing Campaigns">
<field name="name" string="Campaigns"/> <field name="name" string="Campaigns"/>
<field name="category_id"/> <field name="category_ids"/>
<field name="user_id"/> <field name="user_id"/>
<group expand="0" string="Group By..."> <group expand="0" string="Group By...">
<filter string="Stage" name="group_stage_id" <filter string="Stage" name="group_stage_id"
context="{'group_by': 'stage_id'}"/> context="{'group_by': 'stage_id'}"/>
<filter string="Responsible" name="group_user_id" <filter string="Responsible" name="group_user_id"
context="{'group_by': 'user_id'}"/> context="{'group_by': 'user_id'}"/>
<filter string="Category" name="group_category_id"
context="{'group_by': 'category_id'}"/>
</group> </group>
</search> </search>
</field> </field>
@ -492,18 +447,20 @@
<field name="name"/> <field name="name"/>
<field name="user_id"/> <field name="user_id"/>
<field name="stage_id"/> <field name="stage_id"/>
<field name="category_id"/> <field name="category_ids"/>
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form"> <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form">
<field name="name">mail.mass_mailing.campaign.form</field> <field name="name">mail.mass_mailing.campaign.form</field>
<field name="model">mail.mass_mailing.campaign</field> <field name="model">mail.mass_mailing.campaign</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Mass Mailing Campaign" version="7.0"> <form string="Mass Mailing Campaign" version="7.0">
<header> <header>
<button name="action_new_mailing" type="object" class="oe_highlight" string="New Mailing"/> <button name="%(action_view_mass_mailings_from_campaign)d" type="action" class="oe_highlight" string="New Mailing"/>
<field name="stage_id" widget="statusbar" clickable="True"/> <field name="stage_id" widget="statusbar" clickable="True"/>
</header> </header>
<sheet> <sheet>
@ -511,8 +468,7 @@
<group> <group>
<field name="name"/> <field name="name"/>
<field name="user_id"/> <field name="user_id"/>
<field name="category_id"/> <field name="category_ids"/>
<field name="ab_testing"/>
</group> </group>
<group> <group>
<field name="total" invisible="1"/> <field name="total" invisible="1"/>
@ -583,7 +539,7 @@
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" <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"/> 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> <h3 style="margin-bottom: 8px;"><field name="name"/></h3>
<span class="oe_tag"><field name="category_id"/></span> <span class="oe_tag"><field name="category_ids"/></span>
<a name="%(action_view_mass_mailings_from_campaign)d" type="action" <a name="%(action_view_mass_mailings_from_campaign)d" type="action"
class="oe_mailings"> class="oe_mailings">
<h4 style="margin-top: 8px;"><t t-raw="record.mass_mailing_ids.raw_value.length"/> Mailings</h4> <h4 style="margin-top: 8px;"><t t-raw="record.mass_mailing_ids.raw_value.length"/> Mailings</h4>
@ -673,7 +629,6 @@
<group> <group>
<field name="mass_mailing_id"/> <field name="mass_mailing_id"/>
<field name="mass_mailing_campaign_id"/> <field name="mass_mailing_campaign_id"/>
<field name="template_id"/>
<field name="model"/> <field name="model"/>
<field name="res_id"/> <field name="res_id"/>
</group> </group>
@ -694,11 +649,5 @@
parent="base.menu_email" sequence="50" parent="base.menu_email" sequence="50"
action="action_view_mail_mail_statistics"/> action="action_view_mail_mail_statistics"/>
<!-- MISC -->
<!-- Mailing List Create Wizard -->
<menuitem name="Create a new List" id="menu_mail_mass_mailing_create"
parent="mass_mailing_list" sequence="10"
action="action_mail_mass_mailing_create"/>
</data> </data>
</openerp> </openerp>

View File

@ -2,19 +2,5 @@
<openerp> <openerp>
<data> <data>
<!-- Create a Mailing List from Partners -->
<act_window name="Create Mailing List"
res_model="mail.mass_mailing.list.confirm"
src_model="res.partner"
view_mode="form"
multi="True"
target="new"
key2="client_action_multi"
id="action_partner_to_mailing_list"
context="{
'default_mass_mailing_id': context.get('default_mass_mailing_id'),
'default_model': context.get('default_model', 'res.partner'),
'default_name': context.get('default_name', False)}"/>
</data> </data>
</openerp> </openerp>

View File

@ -2,4 +2,3 @@
import test_mailing import test_mailing
import mail_compose_message import mail_compose_message
import mailing_list_confirm

View File

@ -50,15 +50,16 @@ class MailComposeMessage(osv.TransientModel):
if wizard.composition_mode == 'mass_mail' and \ if wizard.composition_mode == 'mass_mail' and \
(wizard.mass_mailing_name or wizard.mass_mailing_id) 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)]: wizard.model in [item[0] for item in self.pool['mail.mass_mailing']._get_mailing_model(cr, uid, context=context)]:
if wizard.mailing_list_ids: # list_ids = None
list_ids = [l.id for l in wizard.mailing_list_ids] # if wizard.mailing_list_ids:
if not list_ids: # list_ids = [l.id for l in wizard.mailing_list_ids]
list_ids = [self.pool['mail.mass_mailing.list'].create( # if not list_ids:
cr, uid, { # list_ids = [self.pool['mail.mass_mailing.list'].create(
'name': wizard.mass_mailing_name, # cr, uid, {
'model': wizard.model, # 'name': wizard.mass_mailing_name,
'domain': wizard.active_domain, # 'model': wizard.model,
}, context=context)] # 'domain': wizard.active_domain,
# }, context=context)]
mass_mailing = wizard.mass_mailing_id mass_mailing = wizard.mass_mailing_id
if not mass_mailing: if not mass_mailing:
mass_mailing_id = self.pool['mail.mass_mailing'].create( mass_mailing_id = self.pool['mail.mass_mailing'].create(
@ -68,7 +69,7 @@ class MailComposeMessage(osv.TransientModel):
'template_id': wizard.template_id and wizard.template_id.id or False, 'template_id': wizard.template_id and wizard.template_id.id or False,
'state': 'done', 'state': 'done',
'mailing_type': wizard.model, 'mailing_type': wizard.model,
'contact_list_ids': [(4, list_id) for list_id in list_ids], # 'contact_list_ids': [(4, list_id) for list_id in list_ids],
}, context=context) }, context=context)
mass_mailing = self.pool['mail.mass_mailing'].browse(cr, uid, mass_mailing_id, context=context) mass_mailing = self.pool['mail.mass_mailing'].browse(cr, uid, mass_mailing_id, context=context)
recipient_values = self.pool['mail.mass_mailing'].get_recipients_data(cr, uid, mass_mailing, res_ids, context=context) recipient_values = self.pool['mail.mass_mailing'].get_recipients_data(cr, uid, mass_mailing, res_ids, context=context)

View File

@ -1,57 +0,0 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv, fields
from openerp.tools.translate import _
class MailingListConfirm(osv.TransientModel):
"""A wizard that acts as a confirmation when creating a new mailing list coming
from a list view. This allows to have a lightweight flow to create mailing
lists without having to go through multiple screens."""
_inherit = 'mail.mass_mailing.list'
_name = 'mail.mass_mailing.list.confirm'
_columns = {
'mass_mailing_id': fields.many2one('mail.mass_mailing', 'Mailing'),
}
def create(self, cr, uid, values, context=None):
if context is None:
context = {}
context.update(no_contact_to_list=True)
return super(MailingListConfirm, self).create(cr, uid, values, context=context)
def action_confirm(self, cr, uid, ids, context=None):
wizard = self.browse(cr, uid, ids[0], context=context)
mailing_list_id = self.pool['mail.mass_mailing.list'].create(
cr, uid, {'name': wizard.name, 'model': wizard.model}, context=context)
res = self.pool['mail.mass_mailing.list'].action_add_to_mailing(cr, uid, [mailing_list_id], context=context)
if not res:
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.mass_mailing.list',
'res_id': mailing_list_id,
'context': context,
}
return res
def action_new_list(self, cr, uid, ids, context=None):
wizard = self.browse(cr, uid, ids[0], context=context)
action_id = self.pool['mail.mass_mailing']._get_model_to_list_action_id(cr, uid, wizard.model, context=context)
if wizard.model == 'mail.mass_mailing.contact':
domain = [('list_id', '=', False)]
else:
domain = []
ctx = dict(context, search_default_not_opt_out=True, view_manager_highlight=[action_id], default_name=wizard.name, default_model=wizard.model)
return {
'name': _('Choose Recipients'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': wizard.model,
'context': ctx,
'domain': domain,
}

View File

@ -1,67 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_mail_mass_mailing_list_confirm_form">
<field name="name">mail.mass_mailing.list.confirm.form</field>
<field name="model">mail.mass_mailing.list.confirm</field>
<field name="arch" type="xml">
<form string="Confirm Mailing List" version="7.0">
<group>
<field name="model" invisible="1"/>
<field name="domain" invisible="1"
on_change="on_change_domain(domain, model, context)"/>
<div attrs="{'invisible': [('mass_mailing_id', '=', False)]}" colspan="2">
Adding <field name="contact_nbr" class="oe_inline"/> contacts
to the mailing <field name="mass_mailing_id" class="oe_inline" readonly="1"/>.
</div>
<field name="name" attrs="{'invisible': [('mass_mailing_id', '!=', False)]}"/>
</group>
<footer>
<button string="Confirm" name="action_confirm" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_list_create_form">
<field name="name">mail.mass_mailing.list.confirm.form</field>
<field name="model">mail.mass_mailing.list.confirm</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<form string="Create a Mailing List" version="7.0">
<header>
<button string='Create a New List' class="oe_highlight"
type='object' name='action_new_list'/>
</header>
<sheet>
<group>
<field name="name"/>
<field name="model" widget="radio"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_mail_mass_mailing_list_confirm" model="ir.actions.act_window">
<field name="name">Create a Mailing List</field>
<field name="res_model">mail.mass_mailing.list.confirm</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record id="action_mail_mass_mailing_create" model="ir.actions.act_window">
<field name="name">Create a Mailing List</field>
<field name="res_model">mail.mass_mailing.list.confirm</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_mail_mass_mailing_list_create_form"/>
<field name="target">new</field>
</record>
</data>
</openerp>

View File

@ -6,27 +6,26 @@ from openerp.osv import osv, fields
class TestMassMailing(osv.TransientModel): class TestMassMailing(osv.TransientModel):
_name = 'mail.mass_mailing.test' _name = 'mail.mass_mailing.test'
_description = 'Tets Mailing Wizard' _description = 'Sample Mail Wizard'
_columns = { _columns = {
'email_to': fields.char( 'email_to': fields.char('Recipients', required=True,
'Emails', required=True,
help='Comma-separated list of email addresses.'), help='Comma-separated list of email addresses.'),
'mass_mailing_id': fields.many2one('mail.mass_mailing', 'Mailing', required=True), '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): def send_mail_test(self, cr, uid, ids, context=None):
Mail = self.pool['mail.mail'] Mail = self.pool['mail.mail']
for wizard in self.browse(cr, uid, ids, context=context): for wizard in self.browse(cr, uid, ids, context=context):
mailing = wizard.mass_mailing_id mailing = wizard.mass_mailing_id
if not mailing.template_id:
raise Warning('Please specify on your mailing the template to use.')
test_emails = tools.email_split(wizard.email_to) test_emails = tools.email_split(wizard.email_to)
if not test_emails:
raise Warning('Please specify test email adresses.')
mail_ids = [] mail_ids = []
for test_mail in test_emails: for test_mail in test_emails:
body = mailing.template_id.body_html body = mailing.body_html
unsubscribe_url = self.pool['mail.mass_mailing'].get_unsubscribe_url(cr, uid, mailing.id, 0, email=test_mail, context=context) unsubscribe_url = self.pool['mail.mass_mailing'].get_unsubscribe_url(cr, uid, mailing.id, 0, email=test_mail, context=context)
body = tools.append_content_to_html(body, unsubscribe_url, plaintext=False, container_tag='p') body = tools.append_content_to_html(body, unsubscribe_url, plaintext=False, container_tag='p')
mail_values = { mail_values = {

View File

@ -6,12 +6,15 @@
<field name="name">mail.mass_mailing.test.form</field> <field name="name">mail.mass_mailing.test.form</field>
<field name="model">mail.mass_mailing.test</field> <field name="model">mail.mass_mailing.test</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Mailing Test" version="7.0"> <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> <group>
<field name="email_to"/> <field name="email_to"/>
</group> </group>
<footer> <footer>
<button string="Test Mailing" name="send_mail_test" type="object" class="oe_highlight"/> <button string="Send Sample Mail" name="send_mail_test" type="object" class="oe_highlight"/>
or or
<button string="Cancel" class="oe_link" special="cancel" /> <button string="Cancel" class="oe_link" special="cancel" />
</footer> </footer>

View File

@ -8,20 +8,32 @@ from openerp.addons.web.http import request
class WebsiteEmailDesigner(http.Controller): class WebsiteEmailDesigner(http.Controller):
@http.route('/website_mail/email_designer', type='http', auth="user", website=True, multilang=True) @http.route('/website_mail/email_designer', type='http', auth="user", website=True, multilang=True)
def index(self, model=None, res_id=None, **kw): def index(self, model, res_id, template_model, field_body='body', field_from='email_from', field_subject='name', **kw):
if not model or not model in request.registry or not res_id: if not model or not model in request.registry or not res_id:
return request.redirect('/') return request.redirect('/')
if not 'body' in request.registry[model]._all_columns and not 'body_html' in request.registry[model]._all_columns: # if not 'body' in request.registry[model]._all_columns and not 'body_html' in request.registry[model]._all_columns:
return request.redirect('/') # return request.redirect('/')
obj_ids = request.registry[model].exists(request.cr, request.uid, [res_id], context=request.context) obj_ids = request.registry[model].exists(request.cr, request.uid, [res_id], context=request.context)
if not obj_ids: if not obj_ids:
return request.redirect('/') return request.redirect('/')
cr, uid, context = request.cr, request.uid, request.context
tmpl_obj = request.registry['email.template']
res_id = int(res_id)
tids = tmpl_obj.search(cr, uid, [('model','=',template_model)], context=context)
templates = tmpl_obj.browse(cr, uid, tids, context=context)
print templates
values = { values = {
'object': request.registry[model].browse(request.cr, request.uid, obj_ids[0], context=request.context), 'object': request.registry[model].browse(cr, uid, res_id, context=context),
'model': request.registry[model], 'templates': templates,
'model_name': model, 'model': model,
'res_id': res_id, 'res_id': res_id,
'field_body': field_body,
'field_from': field_from,
'field_subject': field_subject,
} }
print '*', values
return request.website.render("website_mail.designer_index", values) return request.website.render("website_mail.designer_index", values)
@http.route(['/website_mail/snippets'], type='json', auth="user", website=True) @http.route(['/website_mail/snippets'], type='json', auth="user", website=True)

View File

@ -26,13 +26,13 @@ from openerp.tools.translate import _
class EmailTemplate(osv.Model): class EmailTemplate(osv.Model):
_inherit = 'email.template' _inherit = 'email.template'
def _get_website_link(self, cr, uid, ids, name, args, context=None): def action_edit_html(self, cr, uid, ids, name, args, context=None):
return dict((id, _('<a href="website_mail/email_designer?model=email.template&res_id=%d">Open with visual editor</a>') % id) for id in ids) # tde fixme: avoid asserts
assert len(ids)==1, "One and only one ID allowed for this action"
_columns = { url = '/website_mail/email_designer?model=email.template&res_id=%d&field_body=body_html&field_from=email_form&field_subject=name' % (ids[0],)
'website_link': fields.function( return {
_get_website_link, type='text', 'name': _('Edit Template'),
string='Website Link', 'type': 'ir.actions.act_url',
help='Link to the website', 'url': url,
), 'target': 'self',
} }

View File

@ -1,4 +1,19 @@
.js_follow[data-follow='on'] .js_follow_btn , .js_follow[data-follow='on'] .js_follow_btn ,
.js_follow[data-follow='off'] .js_unfollow_btn { .js_follow[data-follow='off'] .js_unfollow_btn {
display: none; display: none;
} }
.email_preview_border {
overflow: hidden !important;
border: 2px solid grey;
height: 300px;
}
.email_preview {
-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;
}

View File

@ -1,17 +1,26 @@
(function () { (function () {
'use strict'; 'use strict';
var website = openerp.website; var website = openerp.website;
website.snippet.BuildingBlock.include({ website.snippet.BuildingBlock.include({
// init: function (parent) { // init: function (parent) {
// this._super.apply(this, arguments); // this._super.apply(this, arguments);
// }, // },
_get_snippet_url: function () { _get_snippet_url: function () {
return '/website_mail/snippets'; return '/website_mail/snippets';
} }
}); });
// Copy the template to the body of the email
$(document).ready(function () {
$('.js_template_set').click(function(ev) {
$('#email_designer').show();
$('#email_template').hide();
$(".js_content", $(this).parent()).children().clone().appendTo('#email_body');
// Todo: switch to edit mode
event.preventDefault();
});
});
})(); })();

View File

@ -7,9 +7,7 @@
<field name="inherit_id" ref="email_template.email_template_form"/> <field name="inherit_id" ref="email_template.email_template_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//h1" position="after"> <xpath expr="//h1" position="after">
<br /> <button string="Edit Template" name="action_edit_html" type="object"/>
<field name="website_link" widget='html' radonly='1'
style='margin: 0px; padding: 0px;'/>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@ -8,47 +8,56 @@
<t t-set="head"> <t t-set="head">
<script type="text/javascript" src="/website_mail/static/src/js/website_email_designer.js"></script> <script type="text/javascript" src="/website_mail/static/src/js/website_email_designer.js"></script>
</t> </t>
<div id="wrap"> <div id="wrap" class="container" t-ignore="True">
<div class="container"> <div id="email_template" t-att-style="(object.body_html and len(templates)&gt;0) and 'display: none'" class="mb32">
<a class="mt16 btn btn-default pull-right"
t-attf-href="/web#return_label=Website&amp;model=#{model}&amp;id=#{res_id}&amp;view_type=form">
Back
</a>
<h1 class="page-header mt16">
Choose an Email Template
</h1>
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-3 col-sm-4 text-center img-border">
<a class="pull-right mt32" <div class="email_preview_border">
t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;view_type=form' % (model_name, res_id)"> <div class="email_preview js_content"/>
<button class="btn btn-primary">Back to Form</button> </div>
</a> <h4>New Template</h4>
<h1 t-field="object.name"/> <button class="btn btn-primary js_template_set">Select</button>
<div class="row" style="width: 600px;"> </div>
<div t-if="'email_from' in model._all_columns" class="row"> <div t-foreach="templates" t-as="template" class="col-md-3 col-sm-4 text-center">
<div class="col-lg-3"><b>Email From</b></div> <div class="email_preview_border">
<div class="col-lg-9"><span t-field="object.email_from"/></div> <div t-field="template.body_html" class="email_preview js_content"/>
</div> </div>
<h4 t-field="template.name"/>
<div t-if="'email_to' in model._all_columns" class="row"> <button class="btn btn-primary js_template_set">Select</button>
<div class="col-lg-3"><b>To (Email)</b></div> </div>
<div class="col-lg-9"><span t-field="object.email_to"/></div> </div>
</div> </div>
<div id="email_designer" t-att-style="not (object.body_html and len(templates)&gt; 0) and 'display: none'" class="mb32">
<div t-if="'partner_to' in model._all_columns" class="row"> <a class="mt16 btn btn-primary pull-right"
<div class="col-lg-3"><b>To (Partners)</b></div> t-attf-href="/web#return_label=Website&amp;model=#{model}&amp;id=#{res_id}&amp;view_type=form">
<div class="col-lg-9"><span t-field="object.partner_to"/></div> Save and Continue
</div> </a>
<h1 class="page-header mt16">
<div t-if="'reply_to' in model._all_columns" class="row"> Design Your Email
<div class="col-lg-3"><b>Reply To</b></div> </h1>
<div class="col-lg-9"><span t-field="object.reply_to"/></div> <div class="form-horizontal">
</div> <div class="form-group">
<label class="col-sm-2 control-label">From:</label>
<div t-if="'subject' in model._all_columns" class="row"> <div class="col-sm-7">
<div class="col-lg-3"><b>Subject</b></div> <span t-field="object.email_from" class="form-control"/>
<div class="col-lg-9"><span t-field="object.subject"/></div> </div>
</div> </div>
<div class="form-group">
<div class="row well"> <label class="col-sm-2 control-label">Subject:</label>
<div t-field="object.body_html" style="position: relative;"/> <div class="col-sm-7">
</div> <span t-field="object.name" class="form-control"/>
</div> </div>
</div> </div>
</div> </div>
<hr/>
<div t-field="object.body_html" id="email_body"/>
</div> </div>
</div> </div>
</t> </t>