install MailChimp Interface

develop
Andreas Osim 2019-11-18 11:54:26 +01:00
parent 7665c80e8f
commit d895483158
70 changed files with 3263 additions and 0 deletions

View File

@ -0,0 +1,3 @@
from . import models
from . import wizard
from . import controllers

View File

@ -0,0 +1,60 @@
{
"name": "Odoo MailChimp Integration",
"version": "11.0.3",
"category": "Marketing",
'summary': 'Integrate & Manage MailChimp Operations from Odoo',
"depends": ["mass_mailing"],
'data': [
'data/ir_cron.xml',
'views/assets.xml',
'views/mailchimp_accounts_view.xml',
'views/mailchimp_lists_view.xml',
'wizard/import_export_operation_view.xml',
'views/mass_mailing_contact_view.xml',
'views/mass_mailing_list_view.xml',
'views/mailchimp_template_view.xml',
'views/mass_mailing_view.xml',
'wizard/mass_mailing_schedule_date_view.xml',
'views/res_partner_views.xml',
'wizard/partner_export_update_wizard.xml',
'security/ir.model.access.csv'
],
'images': ['static/description/mailchimp_odoo.png'],
"author": "Teqstars",
"website": "https://teqstars.com",
'support': 'info@teqstars.com',
'maintainer': 'Teqstars',
"description": """
- Manage your MailChimp operation from Odoo
- Integration mailchimp
- Connector mailchimp
- mailchimp Connector
- Odoo mailchimp Connector
- mailchimp integration
- mailchimp odoo connector
- mailchimp odoo integration
- odoo mailchimp integration
- odoo integration with mailchimp
- odoo teqstars apps
- teqstars odoo apps
- manage audience
- manage champaign
- email Marketing
- mailchimp marketing
- odoo and mailchimp
""",
'demo': [],
'license': 'OPL-1',
'live_test_url':'http://bit.ly/2n7ExKX',
'auto_install': False,
"installable": True,
'application': True,
'qweb': ['static/src/xml/shipstation_dashboard_template.xml'],
"price": "149.00",
"currency": "EUR",
}

View File

@ -0,0 +1 @@
from . import mailchimp

View File

@ -0,0 +1,75 @@
from odoo.http import request
from odoo import http
import logging
import hashlib
_logger = logging.getLogger(__name__)
class MailChimp(http.Controller):
@http.route('/mailchimp/webhook/notification', type='http', auth="public", csrf=False)
def mailchimp_api(self, **kwargs):
# TODO : Work with multi databases.
contact_obj = request.env['mail.mass_mailing.contact'].sudo()
mass_mailling_obj = request.env['mail.mass_mailing'].sudo()
mailchimp_list_obj = request.env['mailchimp.lists'].sudo()
if kwargs.get('data[merges][EMAIL]', False):
email_address = kwargs['data[merges][EMAIL]']
mailchimp_id = kwargs['data[web_id]']
event = kwargs.get('type', False)
contact_id = contact_obj.search([('email', '=', email_address)])
mailchimp_list_id = mailchimp_list_obj.search([('list_id', '=', kwargs['data[list_id]'])])
name = "%s %s" % (kwargs.get('data[merges][FNAME]'), kwargs.get('data[merges][LNAME]'))
md5_email = hashlib.md5(email_address.encode('utf-8')).hexdigest()
merge_field_dict = {}
update_partner_required = True
for custom_field in mailchimp_list_id.merge_field_ids:
tag = custom_field.tag
if custom_field.type == 'address':
address_dict = {
'addr1': kwargs.get('data[merges][{}][addr1]'.format(tag), ''),
'addr2': kwargs.get('data[merges][{}][addr2]'.format(tag), ''),
'city': kwargs.get('data[merges][{}][city]'.format(tag), ''),
'state': kwargs.get('data[merges][{}][state]'.format(tag), ''),
'zip': kwargs.get('data[merges][{}][zip]'.format(tag), ''),
'country': kwargs.get('data[merges][{}][country]'.format(tag), ''),
}
merge_field_dict.update({tag: address_dict})
else:
merge_field_dict.update({tag: kwargs.get('data[merges][{}]'.format(tag), '')})
tag_ids = contact_id.fetch_specific_member_data(mailchimp_list_id, md5_email)
prepared_vals_for_create_partner = mailchimp_list_id._prepare_vals_for_to_create_partner(merge_field_dict)
prepared_vals_for_create_partner.update({'category_id': [(6, 0, tag_ids.ids)]})
if not contact_id:
if prepared_vals_for_create_partner:
mailchimp_list_id.update_partner_detail(name, email_address, prepared_vals_for_create_partner)
update_partner_required = False
contact_id = contact_id.create({'name': name, 'email': email_address, 'country_id': prepared_vals_for_create_partner.get('country_id', False) or False})
if contact_id and kwargs.get('data[action]', '') != 'delete':
if tag_ids or not tag_ids and contact_id.tag_ids:
contact_id.write({'tag_ids': [(6, 0, tag_ids.ids)]})
if update_partner_required:
mailchimp_list_id.update_partner_detail(name, email_address, prepared_vals_for_create_partner)
vals = {'list_id': mailchimp_list_id.odoo_list_id.id, 'contact_id': contact_id.id, 'mailchimp_id': mailchimp_id, 'md5_email': md5_email}
existing_define_list = contact_id.subscription_list_ids.filtered(
lambda x: x.list_id.id == mailchimp_list_id.odoo_list_id.id)
if existing_define_list:
existing_define_list.write(vals)
else:
contact_id.subscription_list_ids.create(vals)
if event == 'unsubscribe':
mass_mailling_obj.update_opt_out_ts(contact_id.email, mailchimp_list_id.odoo_list_id.ids, True)
elif event == 'subscribe':
mass_mailling_obj.update_opt_out_ts(contact_id.email, mailchimp_list_id.odoo_list_id.ids, False)
elif event == 'profile':
name = "%s %s" % (kwargs.get('data[merges][FNAME]'), kwargs.get('data[merges][LNAME]'))
contact_id.write({'name': name, 'email': kwargs.get('data[merges][EMAIL]')})
if kwargs.get('data[action]', '') == 'delete':
if len(contact_id.list_ids) > 1:
contact_id.write({'list_ids': [(3, mailchimp_list_id.odoo_list_id.id)]})
else:
contact_id.unlink()
request._cr.commit()
return 'SUCCESS'
return 'FAILURE'

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="fetch_email_activity" model="ir.cron">
<field name="name">Do Not Delete : Fetch Campaigns Reports From MailChimp</field>
<field name="model_id" ref="mass_mailing.model_mail_mass_mailing"/>
<field name="state">code</field>
<field name="code">model.fetch_email_activity()</field>
<field name='interval_number'>1</field>
<field name='interval_type'>hours</field>
<field name="numbercall">-1</field>
<field name="active">1</field>
</record>
<record id="fetch_member" model="ir.cron">
<field name="name">Do Not Delete : Fetch MailChimp Audience</field>
<field name="model_id" ref="mailchimp.model_mailchimp_lists"/>
<field name="state">code</field>
<field name="code">model.fetch_member_cron()</field>
<field name='interval_number'>1</field>
<field name='interval_type'>hours</field>
<field name="numbercall">-1</field>
<field name="active">1</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,13 @@
from . import mailchimp_lists
from . import mailchimp_accounts
from . import mass_mailing_contact
from . import mass_mailing_list
from . import mailchimp_template
from . import mass_mailing
from . import mail_mail_statistics
from . import mass_mailing_list_contact_rel
from . import res_partner
from . import ir_model
from . import res_partner_category
from . import mailchimp_segments
from . import mailchimp_merge_fields

View File

@ -0,0 +1,96 @@
import logging
from odoo import api, fields, models, SUPERUSER_ID, tools, _
from odoo.exceptions import AccessError, UserError, ValidationError
_logger = logging.getLogger(__name__)
MODULE_UNINSTALL_FLAG = '_force_unlink'
class IrModelData(models.Model):
_inherit = 'ir.model.data'
@api.model
def _module_data_uninstall(self, modules_to_remove):
"""Deletes all the records referenced by the ir.model.data entries
``ids`` along with their corresponding database backed (including
dropping tables, columns, FKs, etc, as long as there is no other
ir.model.data entry holding a reference to them (which indicates that
they are still owned by another module).
Attempts to perform the deletion in an appropriate order to maximize
the chance of gracefully deleting all records.
This step is performed as part of the full uninstallation of a module.
"""
if not (self._uid == SUPERUSER_ID or self.env.user.has_group('base.group_system')):
raise AccessError(_('Administrator access is required to uninstall a module'))
# enable model/field deletion
self = self.with_context(**{MODULE_UNINSTALL_FLAG: True})
datas = self.search([('module', 'in', modules_to_remove)])
to_unlink = tools.OrderedSet()
undeletable = self.browse([])
for data in datas.sorted(key='id', reverse=True):
model = data.model
res_id = data.res_id
if data.model == 'ir.model' and 'mailchimp' in modules_to_remove:
model_id = self.env[model].browse(res_id).with_context(prefetch_fields=False)
if model_id.model == 'mail.mass_mailing.list_contact_rel':
continue
if data.model == 'ir.model.fields' and 'mailchimp' in modules_to_remove:
field_id = self.env[model].browse(res_id).with_context(prefetch_fields=False)
if field_id.name in ['contact_id', 'list_id']:
continue
to_unlink.add((model, res_id))
def unlink_if_refcount(to_unlink):
undeletable = self.browse()
for model, res_id in to_unlink:
external_ids = self.search([('model', '=', model), ('res_id', '=', res_id)])
if external_ids - datas:
# if other modules have defined this record, we must not delete it
continue
if model == 'ir.model.fields':
# Don't remove the LOG_ACCESS_COLUMNS unless _log_access
# has been turned off on the model.
field = self.env[model].browse(res_id).with_context(
prefetch_fields=False,
)
if not field.exists():
_logger.info('Deleting orphan external_ids %s', external_ids)
external_ids.unlink()
continue
if field.name in models.LOG_ACCESS_COLUMNS and field.model in self.env and self.env[field.model]._log_access:
continue
if field.name == 'id':
continue
_logger.info('Deleting %s@%s', res_id, model)
try:
self._cr.execute('SAVEPOINT record_unlink_save')
self.env[model].browse(res_id).unlink()
except Exception:
_logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
undeletable += external_ids
self._cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
else:
self._cr.execute('RELEASE SAVEPOINT record_unlink_save')
return undeletable
# Remove non-model records first, then model fields, and finish with models
undeletable += unlink_if_refcount(item for item in to_unlink if item[0] not in ('ir.model', 'ir.model.fields', 'ir.model.constraint'))
undeletable += unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model.constraint')
modules = self.env['ir.module.module'].search([('name', 'in', modules_to_remove)])
constraints = self.env['ir.model.constraint'].search([('module', 'in', modules.ids)])
constraints._module_data_uninstall()
undeletable += unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model.fields')
relations = self.env['ir.model.relation'].search([('module', 'in', modules.ids)])
relations._module_data_uninstall()
undeletable += unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model')
(datas - undeletable).unlink()

View File

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class MailMailStats(models.Model):
_inherit = 'mail.mail.statistics'
email = fields.Char(string="Recipient email address")

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
import json
import time
import requests
from email.utils import formataddr
from odoo import api, fields, models, _
from odoo.tools.safe_eval import safe_eval
from odoo.exceptions import ValidationError, Warning
class MailChimpAccounts(models.Model):
_name = "mailchimp.accounts"
name = fields.Char("Name", required=True, copy=False, help="Name of your MailChimp account")
# Authentication
api_key = fields.Char('API Key', required=True, copy=False)
auto_refresh_member = fields.Boolean("Auto Sync Member?", copy=False, default=True)
auto_create_member = fields.Boolean("Auto Create Member?", copy=False, default=True)
auto_create_partner = fields.Boolean("Auto Create Customer?", copy=False, default=False)
list_ids = fields.One2many('mailchimp.lists', 'account_id', string="Lists/Audience", copy=False)
campaign_ids = fields.One2many('mail.mass_mailing', 'mailchimp_account_id', string="Campaigns", copy=False)
_sql_constraints = [
('api_keys_uniq', 'unique(api_key)', 'API keys must be unique per MailChimp Account!'),
]
@api.model
def _send_request(self, request_url, request_data, params=False, method='GET'):
if '-' not in self.api_key:
raise ValidationError(_("MailChimp API key is invalid!"))
if len(self.api_key.split('-')) > 2:
raise ValidationError(_("MailChimp API key is invalid!"))
api_key, dc = self.api_key.split('-')
headers = {
'Content-Type': 'application/json'
}
data = json.dumps(request_data)
api_url = "https://{dc}.api.mailchimp.com/3.0/{url}".format(dc=dc, url=request_url)
try:
req = requests.request(method, api_url, auth=('apikey', api_key), headers=headers, params=params, data=data)
req.raise_for_status()
response_text = req.text
except requests.HTTPError as e:
raise Warning("%s" % req.text)
response = json.loads(response_text) if response_text else {}
return response
@api.multi
def get_refresh_member_action(self):
action = self.env.ref('base.ir_cron_act').read()[0]
refresh_member_cron = self.env.ref('mailchimp.fetch_member')
if refresh_member_cron:
action['views'] = [(False, 'form')]
action['res_id'] = refresh_member_cron.id
else:
raise ValidationError(_("Scheduled action isn't found! Please upgrade app to get it back!"))
return action
def covert_date(self, value):
before_date = value[:19]
coverted_date = time.strptime(before_date, "%Y-%m-%dT%H:%M:%S")
final_date = time.strftime("%Y-%m-%d %H:%M:%S", coverted_date)
return final_date
@api.multi
def import_lists(self):
mailchimp_lists = self.env['mailchimp.lists']
for account in self:
mailchimp_lists.import_lists(account)
return True
@api.multi
def import_templates(self):
mailchimp_templates = self.env['mailchimp.templates']
for account in self:
mailchimp_templates.import_templates(account)
return True
@api.multi
def import_campaigns(self):
mass_mailing_obj = self.env['mail.mass_mailing']
for account in self:
mass_mailing_obj.import_campaigns(account)
return True
@api.one
def test_connection(self):
response = self._send_request('lists', {})
if response:
raise Warning("Test Connection Succeeded")
return True

View File

@ -0,0 +1,541 @@
import logging
import hashlib
from datetime import datetime
_logger = logging.getLogger(__name__)
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
EMAIL_PATTERN = '([^ ,;<@]+@[^> ,;]+)'
unwanted_data = ['_links', 'modules']
replacement_of_key = [('id', 'list_id')]
DATE_CONVERSION = ['date_created', 'last_sub_date', 'last_unsub_date', 'campaign_last_sent']
NOT_REQUIRED_ON_UPDATE = ['color', 'list_id', 'web_id', 'from_name', 'from_email', 'subject', 'partner_id',
'account_id',
'date_created', 'list_rating',
'subscribe_url_short', 'subscribe_url_long', 'beamer_address', 'id', 'display_name',
'create_uid', 'create_date', 'write_uid', 'write_date', '__last_update', '__last_update',
'statistics_ids', 'stats_overview_ids', 'stats_audience_perf_ids', 'stats_campaign_perf_ids',
'stats_since_last_campaign_ids', 'lang_id', 'odoo_list_id', 'last_create_update_date',
'is_update_required', 'contact_ids', 'subscription_contact_ids', 'mailchimp_list_id']
class MassMailingList(models.Model):
_inherit = "mail.mass_mailing.list"
def _compute_contact_nbr(self):
self.env.cr.execute('''
select
list_id, count(*)
from
mail_mass_mailing_contact_list_rel r
left join mail_mass_mailing_contact c on (r.contact_id=c.id)
where
c.opt_out <> true
AND c.is_email_valid = TRUE
AND c.email IS NOT NULL
group by
list_id
''')
data = dict(self.env.cr.fetchall())
for mailing_list in self:
mailing_list.contact_nbr = data.get(mailing_list.id, 0)
contact_nbr = fields.Integer(compute="_compute_contact_nbr", string='Number of Contacts')
class MailChimpLists(models.Model):
_name = "mailchimp.lists"
_inherits = {'mail.mass_mailing.list': 'odoo_list_id'}
_description = "MailChimp Audience"
def _compute_contact_unsub_nbr(self):
self.env.cr.execute('''
select
list_id, count(*)
from
mail_mass_mailing_contact_list_rel r
left join mail_mass_mailing_contact c on (r.contact_id=c.id)
where
COALESCE(c.opt_out,TRUE) = TRUE AND
COALESCE(c.is_email_valid,TRUE) = TRUE
AND c.email IS NOT NULL
group by
list_id
''')
data = dict(self.env.cr.fetchall())
for mailing_list in self:
mailing_list.contact_unsub_nbr = data.get(mailing_list.odoo_list_id.id, 0)
def _compute_contact_cleaned_nbr(self):
self.env.cr.execute('''
select
list_id, count(*)
from
mail_mass_mailing_contact_list_rel r
left join mail_mass_mailing_contact c on (r.contact_id=c.id)
where
COALESCE(c.is_email_valid,FALSE) = FALSE
AND c.email IS NOT NULL
group by
list_id
''')
data = dict(self.env.cr.fetchall())
for mailing_list in self:
mailing_list.contact_cleaned_nbr = data.get(mailing_list.odoo_list_id.id, 0)
def _compute_contact_total_nbr(self):
self.env.cr.execute('''
select
list_id, count(*)
from
mail_mass_mailing_contact_list_rel r
left join mail_mass_mailing_contact c on (r.contact_id=c.id)
where
c.email IS NOT NULL
group by
list_id
''')
data = dict(self.env.cr.fetchall())
for mailing_list in self:
mailing_list.contact_total_nbr = data.get(mailing_list.odoo_list_id.id, 0)
def _is_update_required(self):
for record in self:
if record.write_date and record.last_create_update_date and record.write_date > record.last_create_update_date:
record.is_update_required = True
else:
record.is_update_required = False
# name = fields.Char("Name", required=True, help="This is how the list will be named in MailChimp.")
color = fields.Integer('Color Index', default=0)
list_id = fields.Char("Audience ID", copy=False, readonly=True)
web_id = fields.Char("Website Identification", readonly=True)
partner_id = fields.Many2one('res.partner', string="Contact", ondelete='restrict')
permission_reminder = fields.Text("Permission Reminder", help="Remind recipients how they signed up to your list.")
use_archive_bar = fields.Boolean("Use Archive Bar",
help="Whether campaigns for this list use the Archive Bar in archives by default.")
notify_on_subscribe = fields.Char("Email Subscribe Notifications To",
help="The email address to send subscribe notifications to. \n Additional email addresses must be separated by a comma.")
notify_on_unsubscribe = fields.Char("Email Unsubscribe Notifications To",
help="The email address to send unsubscribe notifications to. \n Additional email addresses must be separated by a comma.")
date_created = fields.Datetime("Creation Date", readonly=True)
list_rating = fields.Selection([('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5')],
"List Rating")
email_type_option = fields.Boolean("Let users pick plain-text or HTML emails.",
help="Whether the list suports multiple formats for emails. When set to true, subscribers can choose whether they want to receive HTML or plain-text emails. When set to false, subscribers will receive HTML emails, with a plain-text alternative backup.")
subscribe_url_short = fields.Char("Subscribe URL Short", readonly=True)
subscribe_url_long = fields.Char("Subscribe URL Long", readonly=True,
help="The full version of this lists subscribe form (host will vary).")
beamer_address = fields.Char("Beamer Address", readonly=True)
visibility = fields.Selection([('pub', 'Yes. My campaigns are public, and I want them to be discovered.'),
('prv', 'No, my campaigns for this list are not public.')],
help="Whether this list is public or private.")
double_optin = fields.Boolean("Enable double opt-in",
help="Whether or not to require the subscriber to confirm subscription via email.")
has_welcome = fields.Boolean("Send a Final Welcome Email",
help="Whether or not this list has a welcome automation connected. Welcome Automations: welcomeSeries, singleWelcome, emailFollowup.")
marketing_permissions = fields.Boolean("Enable GDPR fields",
help="Whether or not the list has marketing permissions (eg. GDPR) enabled.")
from_name = fields.Char("Default From Name", required=True)
from_email = fields.Char("Default From Email Address", required=True)
subject = fields.Char("Default Email Subject")
lang_id = fields.Many2one('res.lang', string="Language")
odoo_list_id = fields.Many2one('mail.mass_mailing.list', string='Odoo Mailing List', required=True,
ondelete="cascade")
contact_unsub_nbr = fields.Integer(compute="_compute_contact_unsub_nbr", string='Number of Unsubscribed Contacts')
contact_cleaned_nbr = fields.Integer(compute="_compute_contact_cleaned_nbr", string='Number of Cleaned Contacts')
contact_total_nbr = fields.Integer(compute="_compute_contact_total_nbr", string='Number of Total Contacts')
statistics_ids = fields.One2many('mailchimp.lists.stats', 'list_id', string="Statistics",
help="Stats for the list. Many of these are cached for at least five minutes.")
stats_overview_ids = fields.One2many('mailchimp.lists.stats', 'list_id', string="Statistics",
help="Stats for the list. Many of these are cached for at least five minutes.")
stats_audience_perf_ids = fields.One2many('mailchimp.lists.stats', 'list_id', string="Statistics",
help="Stats for the list. Many of these are cached for at least five minutes.")
stats_campaign_perf_ids = fields.One2many('mailchimp.lists.stats', 'list_id', string="Statistics",
help="Stats for the list. Many of these are cached for at least five minutes.")
stats_since_last_campaign_ids = fields.One2many('mailchimp.lists.stats', 'list_id', string="Statistics",
help="Stats for the list. Many of these are cached for at least five minutes.")
account_id = fields.Many2one("mailchimp.accounts", string="Account", required=True)
last_create_update_date = fields.Datetime("Last Create Update")
write_date = fields.Datetime('Update on', index=True, readonly=True)
is_update_required = fields.Boolean("Update Required?", compute="_is_update_required")
member_since_last_changed = fields.Datetime("Fetch Member Since Last Change", copy=False)
segment_ids = fields.One2many("mailchimp.segments", 'list_id', string="Segments", copy=False)
merge_field_ids = fields.One2many("mailchimp.merge.fields", 'list_id', string="Merge Fields", copy=False)
@api.multi
def unlink(self):
odoo_lists = self.mapped('odoo_list_id')
super(MailChimpLists, self).unlink()
return odoo_lists.unlink()
def action_view_recipients(self):
action = self.env.ref('mass_mailing.action_view_mass_mailing_contacts_from_list').read()[0]
action['domain'] = [('list_ids', 'in', self.odoo_list_id.ids)]
ctx = {'default_list_ids': [self.odoo_list_id.id]}
if self.env.context.get('show_total', False):
action['context'] = ctx
if self.env.context.get('show_sub', False):
ctx.update({'search_default_not_opt_out': 1})
action['context'] = ctx
if self.env.context.get('show_unsub', False):
ctx.update({'search_default_unsub_contact': 1})
action['context'] = ctx
if self.env.context.get('show_cleaned', False):
ctx.update({'search_default_cleaned_contact': 1})
action['context'] = ctx
return action
@api.model
def _prepare_vals_for_update(self):
self.ensure_one()
prepared_vals = {}
for field_name in self.fields_get_keys():
if hasattr(self, field_name) and field_name not in NOT_REQUIRED_ON_UPDATE:
prepared_vals.update({field_name: getattr(self, field_name)})
partner_id = self.partner_id
prepared_vals['contact'] = {'company': partner_id.name, 'address1': partner_id.street,
'address2': partner_id.street2, 'city': partner_id.city,
'state': partner_id.state_id.name or '', 'zip': partner_id.zip,
'country': partner_id.country_id.code, 'phone': partner_id.phone}
prepared_vals['campaign_defaults'] = {'from_name': self.from_name, 'from_email': self.from_email,
'subject': self.subject or '', 'language': self.lang_id.iso_code or ''}
return prepared_vals
@api.multi
def export_in_mailchimp(self):
for list in self:
prepared_vals = list._prepare_vals_for_update()
response = list.account_id._send_request('lists', prepared_vals, method='POST')
return True
@api.multi
def update_in_mailchimp(self):
for list in self:
prepared_vals = list._prepare_vals_for_update()
response = list.account_id._send_request('lists/%s' % list.list_id, prepared_vals, method='PATCH')
list.write({'last_create_update_date': fields.Datetime.now()})
return True
@api.model
def _find_partner(self, location):
partners = self.env['res.partner']
state = self.env['res.country.state']
domain = []
if 'address1' in location and 'city' in location and 'company' in location:
domain.append(('name', '=', location['company']))
domain.append(('street', '=', location['address1']))
domain.append(('city', '=', location['city']))
if location.get('state'):
domain.append(('state_id.name', '=', location['state']))
if location.get('zip'):
domain.append(('zip', '=', location['zip']))
partners = partners.search(domain, limit=1)
if not partners:
country_id = self.env['res.country'].search([('code', '=', location['country'])], limit=1)
if country_id and location['state']:
state = self.env['res.country.state'].search(
['|', ('name', '=', location['state']), ('code', '=', location['state']),
('country_id', '=', country_id.id)], limit=1)
elif location['state']:
state = self.env['res.country.state'].search(
['|', ('name', '=', location['state']), ('code', '=', location['state'])],
limit=1)
location.update({'name': location.pop('company'), 'street': location.pop('address1'),
'street2': location.pop('address2'), 'state_id': state.id, 'country_id': country_id.id})
partners = partners.create(location)
return partners
@api.multi
def create_or_update_list(self, values_dict, account=False):
list_id = values_dict.get('id')
existing_list = self.search([('list_id', '=', list_id)])
stats = values_dict.pop('stats', {})
values_dict.update(values_dict.pop('campaign_defaults'))
lang_id = self.env['res.lang'].search([('iso_code', '=', values_dict.get('language', 'en'))])
for item in unwanted_data:
values_dict.pop(item)
for old_key, new_key in replacement_of_key:
values_dict[new_key] = values_dict.pop(old_key)
for item in DATE_CONVERSION:
if values_dict.get(item, False) == '':
values_dict[item] = False
if values_dict.get(item, False):
values_dict[item] = account.covert_date(values_dict.get(item))
values_dict.update({'account_id': account.id})
partner = self._find_partner(values_dict.pop('contact'))
values_dict.update(
{'partner_id': partner.id, 'lang_id': lang_id.id, 'list_rating': str(values_dict.pop('list_rating', '0'))})
if not existing_list:
existing_list = self.create(values_dict)
else:
existing_list.write(values_dict)
existing_list.create_or_update_statistics(stats)
# existing_list.fetch_members()
existing_list.fetch_segments()
existing_list.fetch_merge_fields()
existing_list.write({'last_create_update_date': fields.Datetime.now()})
return True
@api.multi
def import_lists(self, account=False):
if not account:
raise Warning("MailChimp Account not defined to import lists")
response = account._send_request('lists', {}, method='GET')
for list in response.get('lists'):
self.create_or_update_list(list, account=account)
return True
@api.one
def refresh_list(self):
if not self.account_id:
raise Warning("MailChimp Account not defined to Refresh list")
response = self.account_id._send_request('lists/%s' % self.list_id, {})
self.create_or_update_list(response, account=self.account_id)
return True
@api.multi
def create_or_update_statistics(self, stats):
self.ensure_one()
self.statistics_ids.unlink()
for item in DATE_CONVERSION:
if stats.get(item, False):
stats[item] = self.account_id.covert_date(stats.get(item))
else:
stats[item] = False
self.write({'statistics_ids': [(0, 0, stats)]})
return True
@api.one
def fetch_merge_fields(self):
mailchimp_merge_field_obj = self.env['mailchimp.merge.fields']
if not self.account_id:
raise Warning("MailChimp Account not defined to Fetch Merge Field list")
count = 1000
offset = 0
merge_field_list = []
prepared_vals = {}
while True:
prepared_vals.update({'count': count, 'offset': offset,
'fields': 'merge_fields.merge_id,merge_fields.tag,merge_fields.name,merge_fields.type,merge_fields.required,merge_fields.default_value,merge_fields.public,merge_fields.display_order,merge_fields.list_id,merge_fields.options'})
response = self.account_id._send_request('lists/%s/merge-fields' % self.list_id, {}, params=prepared_vals)
if len(response.get('merge_fields')) == 0:
break
if isinstance(response.get('merge_fields'), dict):
merge_field_list = [response.get('merge_fields')]
else:
merge_field_list += response.get('merge_fields')
offset = offset + 1000
for merge_field in merge_field_list:
if not merge_field.get('merge_id', False):
continue
merge_field_id = mailchimp_merge_field_obj.search([('merge_id', '=', merge_field.get('merge_id')), ('list_id', '=', self.id)])
merge_field.update({'list_id': self.id})
if merge_field.get('options', {}).get('date_format'):
date_format = merge_field.pop('options', {}).get('date_format')
date_format = date_format.replace("MM", '%m')
date_format = date_format.replace("DD", '%d')
date_format = date_format.replace("YYYY", '%Y')
merge_field.update({'date_format': date_format})
if not merge_field_id:
mailchimp_merge_field_obj.create(merge_field)
if merge_field_id:
merge_field_id.write(merge_field)
return merge_field_list
@api.one
def fetch_segments(self):
mailchimp_segments_obj = self.env['mailchimp.segments']
if not self.account_id:
raise Warning("MailChimp Account not defined to Fetch Segments list")
count = 1000
offset = 0
segments_list = []
prepared_vals = {}
while True:
prepared_vals.update({'count': count, 'offset': offset})
response = self.account_id._send_request('lists/%s/segments' % self.list_id, {}, params=prepared_vals)
if len(response.get('segments')) == 0:
break
if isinstance(response.get('segments'), dict):
segments_list = [response.get('segments')]
else:
segments_list += response.get('segments')
offset = offset + 1000
for segment in segments_list:
if not segment.get('id', False):
continue
segment_id = mailchimp_segments_obj.search([('mailchimp_id', '=', segment.get('id'))])
name = segment.get('name')
if segment.get('type') == 'static':
name = "Tags : %s" % name
vals = {'mailchimp_id': segment.get('id'), 'name': name, 'list_id': self.id}
if not segment_id:
mailchimp_segments_obj.create(vals)
if segment_id:
segment_id.write(vals)
return segments_list
def _prepare_vals_for_to_create_partner(self, merge_field_vals):
prepared_vals = {}
for custom_field in self.merge_field_ids:
if custom_field.type == 'address':
address_dict = merge_field_vals.get(custom_field.tag)
if not address_dict:
continue
state_id = False
country = self.env['res.country'].search([('code', '=', address_dict.get('country', ''))], limit=1)
if country:
state_id = self.env['res.country.state'].search(
['|', ('name', '=', address_dict['state']),
('code', '=', address_dict['state']),
('country_id', '=', country.id)], limit=1)
prepared_vals.update({
'street': address_dict.get('addr1', ''),
'street2': address_dict.get('addr2', ''),
'city': address_dict.get('city', ''),
'zip': address_dict.get('zip', ''),
'state_id': state_id.id if state_id else False,
'country_id': country.id,
})
elif custom_field.tag in ['FNAME', 'LNAME'] and not prepared_vals.get('name', False):
prepared_vals.update({'name': "%s %s" % (merge_field_vals.get('FNAME'), merge_field_vals.get('LNAME'))})
elif custom_field.field_id and custom_field.type in ['date', 'birthday']:
value = merge_field_vals.get(custom_field.tag)
if value and custom_field.field_id.ttype in ['date']:
if len(value.split('/')) > 1:
value = datetime.strptime(value, custom_field.date_format).strftime(DEFAULT_SERVER_DATE_FORMAT)
prepared_vals.update({custom_field.field_id.name: value or False})
elif custom_field.field_id:
prepared_vals.update({custom_field.field_id.name: merge_field_vals.get(custom_field.tag)})
return prepared_vals
@api.one
def fetch_members(self):
mailing_contact_obj = self.env['mail.mass_mailing.contact']
state = self.env['res.country.state']
country = self.env['res.country']
if not self.account_id:
raise Warning("MailChimp Account not defined to Fetch Member list")
if not self.merge_field_ids:
return True
count = 1000
offset = 0
members_list = []
members_list_to_create = []
prepared_vals = {}
if self.member_since_last_changed:
prepared_vals.update({'since_last_changed': datetime.strptime(self.member_since_last_changed, DEFAULT_SERVER_DATETIME_FORMAT).strftime("%Y-%m-%dT%H:%M:%S+00:00")})
while True:
prepared_vals.update({'count': count, 'offset': offset, 'fields': 'members.email_address,members.merge_fields,members.tags,members.web_id,members.status'})
response = self.account_id._send_request('lists/%s/members' % self.list_id, {}, params=prepared_vals)
if len(response.get('members')) == 0:
break
# if isinstance(response.get('members'), dict):
# members_list += [response.get('members')]
if isinstance(response.get('members'), dict):
members_data = [response.get('members')]
else:
members_data = response.get('members')
offset = offset + 1000
for member in members_data:
if not member.get('email_address', False):
continue
update_partner_required = True
contact_id = mailing_contact_obj.search([('email', '=', member.get('email_address'))])
create_vals = member.get('merge_fields')
prepared_vals_for_create_partner = self._prepare_vals_for_to_create_partner(create_vals)
name = "%s %s" % (create_vals.pop('FNAME'), create_vals.pop('LNAME'))
tag_ids = self.env['res.partner.category']
tag_list = member.get('tags')
if tag_list:
tag_ids = self.env['res.partner.category'].create_or_update_tags(tag_list)
prepared_vals_for_create_partner.update({'category_id': [(6, 0, tag_ids.ids)]})
if not contact_id:
if not self.account_id.auto_create_member:
continue
self.update_partner_detail(name, member.get('email_address'), prepared_vals_for_create_partner)
update_partner_required = False
contact_id = mailing_contact_obj.create(
{'name': name, 'email': member.get('email_address'),
'country_id': prepared_vals_for_create_partner.get('country_id', False) or False})
if contact_id:
md5_email = hashlib.md5(member.get('email_address').encode('utf-8')).hexdigest()
vals = {'list_id': self.odoo_list_id.id, 'contact_id': contact_id.id,
'mailchimp_id': member.get('web_id'), 'md5_email': md5_email}
status = member.get('status', '')
if update_partner_required:
self.update_partner_detail(name, member.get('email_address'), prepared_vals_for_create_partner)
contact_vals = {'opt_out': True} if status == 'unsubscribed' else {'opt_out': False}
if status == 'cleaned':
contact_vals.update({'is_email_valid': False, 'opt_out': True})
contact_vals.update({'tag_ids': [(6, 0, tag_ids.ids)]})
contact_id.write(contact_vals)
existing_define_list = contact_id.subscription_list_ids.filtered(
lambda x: x.list_id.id == self.odoo_list_id.id)
if existing_define_list:
existing_define_list.write(vals)
else:
contact_id.subscription_list_ids.create(vals)
self._cr.commit()
self.write({'member_since_last_changed': fields.Datetime.now()})
return members_list
@api.multi
def update_partner_detail(self, name, email, partner_detail):
query = """
SELECT id
FROM res_partner
WHERE LOWER(substring(email, '([^ ,;<@]+@[^> ,;]+)')) = LOWER(substring('{}', '([^ ,;<@]+@[^> ,;]+)'))""".format(email)
self._cr.execute(query)
partner_id = self._cr.fetchone()
partner_id = partner_id[0] if partner_id else False
if partner_id:
partner_id = self.env['res.partner'].browse(partner_id)
if partner_detail:
partner_id.write(partner_detail)
else:
if self.account_id.auto_create_member and self.account_id.auto_create_partner:
partner_detail.update({
'email': email,
'is_company': False,
'type': 'contact',
})
self.env['res.partner'].create(partner_detail)
return True
@api.model
def fetch_member_cron(self):
account_obj = self.env['mailchimp.accounts']
for record in account_obj.search([('auto_refresh_member', '=', True)]):
list_ids = self.search([('account_id', '=', record.id)])
for list in list_ids:
list.fetch_members()
return True
class MailChimpListsStats(models.Model):
_name = "mailchimp.lists.stats"
_description = "MailChimp Statistics"
list_id = fields.Many2one("mailchimp.lists", string="MailChimp List", required=True, ondelete='cascade')
member_count = fields.Integer("Subscribed Count")
unsubscribe_count = fields.Integer("Unsubscribe Count")
cleaned_count = fields.Integer("Cleaned Count")
member_count_since_send = fields.Integer("Subscribed Count")
unsubscribe_count_since_send = fields.Integer("Unsubscribe Count")
cleaned_count_since_send = fields.Integer("Cleaned Count")
campaign_count = fields.Integer("Campaign Count")
campaign_last_sent = fields.Datetime("Campaign Last Sent")
merge_field_count = fields.Integer("Merge Count")
avg_sub_rate = fields.Float("Average Subscription Rate")
avg_unsub_rate = fields.Float("Average Unsubscription Rate")
target_sub_rate = fields.Float("Average Subscription Rate")
open_rate = fields.Float("Open Rate")
click_rate = fields.Float("Click Rate")
last_sub_date = fields.Datetime("Date of Last Subscribe")
last_unsub_date = fields.Datetime("Date of Last Unsubscribe")

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class MailChimpMergeFields(models.Model):
_name = "mailchimp.merge.fields"
name = fields.Char("Name", required=True, copy=False, help="Name of your MailChimp Merge Fields")
merge_id = fields.Char("Merge ID", readonly=True, copy=False)
tag = fields.Char("Merge Field Tag", help="The tag used in Mailchimp campaigns and for the /members endpoint.")
type = fields.Selection(
[('text', 'Text'), ('number', 'Number'), ('address', 'Address'), ('phone', 'Phone'), ('date', 'Date'), ('radio', 'Radio'), ('dropdown', 'Dropdown'), ('birthday', 'Birthday'), ('zip', 'Zip'), ('imageurl', 'ImageURL'), ('url', 'URL')])
date_format = fields.Char('Date Format', copy=False)
required = fields.Boolean("Required?", copy=False, help="Merge field is required or not.")
public = fields.Boolean("Visible?", copy=False, help="Whether the merge field is displayed on the signup form.")
default_value = fields.Char("Default Value", help="The default value for the merge field if null.")
display_order = fields.Char("Display Order", help="The order that the merge field displays on the list signup form.")
list_id = fields.Many2one("mailchimp.lists", string="Associated MailChimp List", ondelete='cascade', required=True, copy=False)
field_id = fields.Many2one('ir.model.fields', string='Odoo Field', help="""Odoo will fill value of selected field while contact is going to export or update""", domain="[('model_id.model', '=', 'res.partner')]")
_sql_constraints = [
('merge_id_list_id_uniq', 'unique(merge_id, list_id)', 'Merge ID must be unique per MailChimp Lists!'),
]

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class MailChimpSegments(models.Model):
_name = "mailchimp.segments"
name = fields.Char("Name", required=True, copy=False, help="Name of your MailChimp Segments")
mailchimp_id = fields.Char("MailChimp ID", readonly=True, copy=False)
list_id = fields.Many2one("mailchimp.lists", string="Associated MailChimp List", ondelete='cascade', required=True, copy=False)
_sql_constraints = [
('mailchimp_id_uniq', 'unique(mailchimp_id)', 'MailChimp ID must be unique per MailChimp Segments!'),
]

View File

@ -0,0 +1,64 @@
from odoo import api, fields, models, _
REPLACEMENT_OF_KEY = [('id', 'template_id')]
DATE_CONVERSION = ['date_created', 'date_edited']
UNWANTED_DATA = ['_links', 'created_by', 'edited_by', 'thumbnail']
class MailChimpTemplates(models.Model):
_name = "mailchimp.templates"
_description = "Templates"
name = fields.Char("Name", required=True, help="The name of the template.")
template_id = fields.Char("Template ID", copy=False)
type = fields.Selection([('user', 'User'), ('gallery', 'Gallery'), ('base', 'Base')], default='user', copy=False,
help="The type of template (user, base, or gallery).")
drag_and_drop = fields.Boolean("Drag and Drop", help="Whether the template uses the drag and drop editor.")
responsive = fields.Boolean("Responsive", help="Whether the template contains media queries to make it responsive.")
category = fields.Char("Template Category", help="If available, the category the template is listed in.")
date_created = fields.Datetime("Created On")
date_edited = fields.Datetime("Edited On")
active = fields.Boolean("Active", default=True)
folder_id = fields.Char("Folder ID", help="The id of the folder the template is currently in.")
share_url = fields.Char("Share URL", help="The URL used for template sharing")
account_id = fields.Many2one("mailchimp.accounts", string="Account", required=True, ondelete='cascade')
@api.multi
def create_or_update_template(self, values_dict, account=False):
template_id = values_dict.get('id')
existing_list = self.search([('template_id', '=', template_id)])
for item in UNWANTED_DATA:
values_dict.pop(item)
for old_key, new_key in REPLACEMENT_OF_KEY:
values_dict[new_key] = values_dict.pop(old_key)
for item in DATE_CONVERSION:
if values_dict.get(item, False) == '':
values_dict[item] = False
if values_dict.get(item, False):
values_dict[item] = account.covert_date(values_dict.get(item))
values_dict.update({'account_id': account.id})
if not existing_list:
existing_list = self.create(values_dict)
else:
existing_list.write(values_dict)
return True
@api.multi
def import_templates(self, account=False):
if not account:
raise Warning("MailChimp Account not defined to import templates")
count = 1000
offset = 0
template_list = []
while True:
prepared_vals = {'count': count, 'offset': offset}
response = account._send_request('templates', {}, params=prepared_vals)
if len(response.get('templates')) == 0:
break
if isinstance(response.get('templates'), dict):
template_list += [response.get('templates')]
template_list += response.get('templates')
offset = offset + 1000
for template_dict in template_list:
self.create_or_update_template(template_dict, account=account)
return True

View File

@ -0,0 +1,348 @@
from datetime import datetime, timedelta
from odoo.tools.safe_eval import safe_eval
from odoo import api, fields, models, tools, _
from odoo.exceptions import Warning, ValidationError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from email.utils import formataddr, parseaddr
REPLACEMENT_OF_KEY = [('id', 'mailchimp_id'), ('create_time', 'create_date'), ('send_time', 'sent_date'),
('type', 'mailchimp_champ_type')]
DATE_CONVERSION = ['create_date', 'sent_date']
UNWANTED_DATA = ['_links', 'created_by', 'edited_by', 'thumbnail']
class MassMailing(models.Model):
_inherit = "mail.mass_mailing"
create_date = fields.Datetime("Created on", readonly=True, index=True)
mailchimp_template_id = fields.Many2one('mailchimp.templates', "MailChimp Template", copy=False)
mailchimp_account_id = fields.Many2one('mailchimp.accounts', string="MailChimp Account",
related="mailchimp_template_id.account_id", store=True)
mailchimp_list_id = fields.Many2one("mailchimp.lists", string="MailChimp List")
mailchimp_id = fields.Char("MailChimp ID", copy=False)
mailchimp_segment_id = fields.Many2one('mailchimp.segments', string="MailChimp Segments", copy=False)
mailchimp_champ_type = fields.Selection(
[('regular', 'Regular'), ('plaintext', 'Plain Text'), ('absplit', 'AB Split'), ('rss', 'RSS'),
('variate', 'Variate')],
default='regular', string="Type")
def update_opt_out_ts(self, email, list_ids, value):
if len(list_ids) > 0:
model = self.env['mail.mass_mailing.contact'].with_context(active_test=False)
records = model.search([('email', '=ilike', email), ('list_ids', 'in', list_ids)])
records.write({'opt_out': value})
@api.multi
def action_schedule_date(self):
self.ensure_one()
action = self.env.ref('mailchimp.mass_mailing_schedule_date_action').read()[0]
action['context'] = dict(self.env.context, default_mass_mailing_id=self.id)
return action
@api.model
def fetch_send_to_activity(self):
self.ensure_one()
account = self.mailchimp_template_id.account_id
if not account:
return True
count = 1000
offset = 0
sent_to_lists = []
while True:
prepared_vals = {'count': count, 'offset': offset,
'fields': 'sent_to.status,sent_to.email_address'}
response = account._send_request(
'reports/%s/sent-to' % self.mailchimp_id, {}, params=prepared_vals)
if len(response.get('sent_to')) == 0:
break
if isinstance(response.get('sent_to'), dict):
sent_to_lists += [response.get('sent_to')]
sent_to_lists += response.get('sent_to')
offset = offset + 1000
return sent_to_lists
@api.multi
def process_send_to_activity_report(self):
self.ensure_one()
stat_obj = self.env['mail.mail.statistics']
sent_to_lists = self.fetch_send_to_activity()
if not sent_to_lists:
return True
domain = safe_eval(self.mailing_domain)
contact_ids = self.env[self.mailing_model_real].search(domain)
for record_dict in sent_to_lists:
prepared_vals = {}
email_address = record_dict.get('email_address')
status = record_dict.get('status')
if status == 'sent':
prepared_vals.update({'sent': self.sent_date, 'scheduled': self.sent_date, 'bounced': False})
elif status in ['hard', 'soft']:
prepared_vals.update({'bounced': self.sent_date, 'sent': self.sent_date, 'scheduled': self.sent_date})
existing = self.statistics_ids.filtered(lambda x: x.email == email_address)
if existing:
existing.write(prepared_vals)
else:
res_id = contact_ids.filtered(lambda x: x.email == email_address)
prepared_vals.update({
'model': self.mailing_model_real,
'res_id': res_id.id,
'mass_mailing_id': self.id,
'email': email_address,
})
self.statistics_ids.create(prepared_vals)
return sent_to_lists
@api.multi
def process_email_activity_report(self):
self.ensure_one()
self.process_send_to_activity_report()
account = self.mailchimp_template_id.account_id
count = 1000
offset = 0
email_lists = []
while True:
prepared_vals = {'count': count, 'offset': offset, 'fields': 'emails.email_address,emails.activity'}
response = account._send_request(
'reports/%s/email-activity' % self.mailchimp_id, {}, params=prepared_vals)
if len(response.get('emails')) == 0:
break
if isinstance(response.get('emails'), dict):
email_lists += [response.get('emails')]
email_lists += response.get('emails')
offset = offset + 1000
for email in email_lists:
email_address = email.get('email_address')
activities = email.get('activity')
if not activities:
continue
prepared_vals = {}
for acitvity in activities:
action = acitvity.get('action')
if action == 'open':
prepared_vals.update({'opened': account.covert_date(acitvity.get('timestamp'))})
elif action == 'click':
prepared_vals.update({'clicked': account.covert_date(acitvity.get('timestamp'))})
elif action == 'bounce':
prepared_vals.update({'bounced': account.covert_date(acitvity.get('timestamp'))})
existing = self.statistics_ids.filtered(lambda x: x.email == email_address)
if existing:
existing.write(prepared_vals)
@api.model
def fetch_email_activity(self):
stat_obj = self.env['mail.mail.statistics']
sent_date = datetime.today() - timedelta(days=30)
sent_date = sent_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
for record in self.search([('state', 'not in', ['draft', 'in_queue']), ('mailchimp_id', '!=', False), ('sent_date', '>=', sent_date)]):
record.fetch_campaign()
# record.process_email_activity_report()
return True
@api.multi
def create_or_update_campaigns(self, values_dict, account=False):
fetch_needed = False
list_obj = self.env['mailchimp.lists']
template_obj = self.env['mailchimp.templates']
mailchimp_id = values_dict.get('id')
settings_dict = values_dict.get('settings')
recipients_dict = values_dict.get('recipients')
list_id = recipients_dict.get('list_id')
template_id = settings_dict.get('template_id')
if list_id:
list_obj = list_obj.search([('list_id', '=', list_id)]).odoo_list_id
if template_id:
template_obj = template_obj.search([('template_id', '=', template_id), ('account_id', '=', account.id)])
status = values_dict.get('status')
subject_line = settings_dict.get('subject_line') or settings_dict.get('title')
try:
email_from = formataddr((settings_dict.get('from_name'), settings_dict.get('reply_to')))
except Exception as e:
email_from = self.env['mail.message']._get_default_from()
prepared_vals = {
'create_date': values_dict.get('create_time'),
'sent_date': values_dict.get('send_time'),
'name': subject_line,
'mailchimp_id': mailchimp_id,
'mailing_model_id': self.env.ref('mass_mailing.model_mail_mass_mailing_list').id,
'contact_list_ids': [(6, 0, list_obj.ids)],
'mailchimp_template_id': template_obj.id,
'mailchimp_champ_type': values_dict.get('type'),
'email_from': email_from,
'reply_to': email_from,
}
if status in ['save', 'paused']:
prepared_vals.update({'state': 'draft'})
elif status == 'schedule':
prepared_vals.update({'state': 'in_queue'})
elif status == 'sending':
prepared_vals.update({'state': 'sending'})
elif status == 'sent':
fetch_needed = True
prepared_vals.update({'state': 'done'})
for item in DATE_CONVERSION:
if prepared_vals.get(item, False) == '':
prepared_vals[item] = False
if prepared_vals.get(item, False):
prepared_vals[item] = account.covert_date(prepared_vals.get(item))
existing_list = self.search([('mailchimp_id', '=', mailchimp_id)])
if not existing_list:
existing_list = self.create(prepared_vals)
self.env.cr.execute("""
UPDATE
mail_mass_mailing
SET create_date = '%s'
WHERE id = %s
""" % (prepared_vals.get('create_date'), existing_list.id))
else:
existing_list.write(prepared_vals)
existing_list._onchange_model_and_list()
existing_list.body_html = False
if fetch_needed:
existing_list.process_email_activity_report()
return True
@api.multi
def fetch_campaign(self):
self.ensure_one()
if not self.mailchimp_id:
return True
account = self.mailchimp_template_id.account_id
params_vals = {
'fields': 'id,type,status,create_time,send_time,settings.template_id,settings.subject_line,settings.title,settings.from_name,settings.reply_to,recipients.list_id'}
response = account._send_request('campaigns/%s' % self.mailchimp_id, {}, params=params_vals)
self.create_or_update_campaigns(response, account=account)
return True
@api.multi
def import_campaigns(self, account=False):
if not account:
raise Warning("MailChimp Account not defined to import Campaigns")
count = 1000
offset = 0
campaigns_list = []
while True:
prepared_vals = {'count': count, 'offset': offset}
response = account._send_request('campaigns', {}, params=prepared_vals)
if len(response.get('campaigns')) == 0:
break
if isinstance(response.get('campaigns'), dict):
campaigns_list += [response.get('campaigns')]
campaigns_list += response.get('campaigns')
offset = offset + 1000
for campaigns_dict in campaigns_list:
self.create_or_update_campaigns(campaigns_dict, account=account)
return True
@api.model
def _prepare_vals_for_export(self):
self.ensure_one()
from_name, from_email = parseaddr(self.email_from)
reply_to_name, reply_to_email = parseaddr(self.reply_to)
settings_dict = {'subject_line': self.name, 'title': self.name, 'from_name': from_name,
'reply_to': reply_to_email, 'template_id': int(self.mailchimp_template_id.template_id)}
prepared_vals = {'type': 'regular',
'recipients': {'list_id': self.contact_list_ids.mailchimp_list_id.list_id, },
'settings': settings_dict}
if self.mailchimp_segment_id.mailchimp_id:
prepared_vals['recipients'].update({'segment_opts': {'saved_segment_id': int(self.mailchimp_segment_id.mailchimp_id)}})
return prepared_vals
@api.one
def export_to_mailchimp(self, account=False):
if self.mailchimp_id:
return True
if not account:
raise Warning("MailChimp Account not defined in selected Template.")
prepared_vals = self._prepare_vals_for_export()
response = account._send_request('campaigns', prepared_vals, method='POST')
if response.get('id', False):
self.write({'mailchimp_id': response['id']})
else:
ValidationError(_("MailChimp Identification wasn't received. Please try again!"))
self._cr.commit()
return True
@api.one
def send_now_mailchimp(self, account=False):
if not account:
raise Warning("MailChimp Account not defined in selected Template.")
response = account._send_request('campaigns/%s/actions/send' % self.mailchimp_id, {}, method='POST')
return True
@api.multi
def send_test_mail_mailchimp(self, test_emails):
self.ensure_one()
self.export_to_mailchimp(self.mailchimp_template_id.account_id)
prepared_vals = {'test_emails': test_emails, 'send_type': 'html'}
response = self.mailchimp_template_id.account_id._send_request('campaigns/%s/actions/test' % self.mailchimp_id,
prepared_vals, method='POST')
return True
@api.multi
def schedule_mailchimp_champaign(self, schedule_date):
self.ensure_one()
self.export_to_mailchimp(self.mailchimp_template_id.account_id)
prepared_vals = {'schedule_time': schedule_date.isoformat()}
response = self.mailchimp_template_id.account_id._send_request(
'campaigns/%s/actions/schedule' % self.mailchimp_id,
prepared_vals, method='POST')
return True
@api.multi
def cancel_mass_mailing(self):
res = super(MassMailing, self).cancel_mass_mailing()
if self.mailchimp_id and self.mailchimp_template_id:
self.mailchimp_template_id.account_id._send_request('campaigns/%s/actions/cancel-send' % self.mailchimp_id,
{}, method='POST')
if self.schedule_date:
self.mailchimp_template_id.account_id._send_request(
'campaigns/%s/actions/unschedule' % self.mailchimp_id,
{}, method='POST')
return res
@api.multi
def put_in_queue(self):
res = super(MassMailing, self).put_in_queue()
for record in self.filtered(lambda x: x.mailchimp_template_id):
if len(record.contact_list_ids) > 1:
raise ValidationError(_("Multiple list is not allowed while going with MailChimp!"))
if record.contact_list_ids.filtered(lambda x: not x.mailchimp_list_id):
raise ValidationError(_("Please provide MailChimp list as you selected MailChimp Template!"))
record.export_to_mailchimp(record.mailchimp_template_id.account_id)
if record.mailchimp_id:
record.send_now_mailchimp(record.mailchimp_template_id.account_id)
record.process_send_to_activity_report()
record.fetch_campaign()
return res
@api.model
def _process_mass_mailing_queue(self):
mass_mailings = self.search(
[('state', 'in', ('in_queue', 'sending')), '|', ('schedule_date', '<', fields.Datetime.now()),
('schedule_date', '=', False)])
for mass_mailing in mass_mailings:
user = mass_mailing.write_uid or self.env.user
mass_mailing = mass_mailing.with_context(**user.sudo(user=user).context_get())
if mass_mailing.mailchimp_id:
mass_mailing.fetch_campaign()
continue
if len(mass_mailing.get_remaining_recipients()) > 0:
mass_mailing.state = 'sending'
mass_mailing.send_mail()
else:
mass_mailing.write({'state': 'done', 'sent_date': fields.Datetime.now()})
@api.onchange('mailing_model_id', 'contact_list_ids')
def _onchange_model_and_list(self):
res = super(MassMailing, self)._onchange_model_and_list()
mailing_domain = []
list_obj = self.env['mailchimp.lists']
list_ids = list_obj.search([('odoo_list_id', 'in', self.contact_list_ids.ids)])
self.mailchimp_list_id = list_ids and list_ids[0] or False
if self.mailchimp_list_id:
self.email_from = formataddr((self.mailchimp_list_id.from_name, self.mailchimp_list_id.from_email))
self.reply_to = formataddr((self.mailchimp_list_id.from_name, self.mailchimp_list_id.from_email))
return res

View File

@ -0,0 +1,192 @@
import re
import hashlib
from datetime import datetime
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
EMAIL_PATTERN = '([^ ,;<@]+@[^> ,;]+)'
def _partner_split_name(partner_name):
return [' '.join(partner_name.split()[:-1]), ' '.join(partner_name.split()[-1:])]
class massMailingContact(models.Model):
_inherit = "mail.mass_mailing.contact"
@api.multi
def get_partner(self, email):
query = """
SELECT id
FROM res_partner
WHERE LOWER(substring(email, '([^ ,;<@]+@[^> ,;]+)')) = LOWER(substring('{}', '([^ ,;<@]+@[^> ,;]+)'))""".format(email)
self._cr.execute(query)
return self._cr.fetchone() or False
@api.model
def create(self, vals):
res = super(massMailingContact, self).create(vals)
if vals.get('email', False):
partner_record = self.get_partner(vals.get('email'))
res.related_partner_id = partner_record
return res
@api.multi
def write(self, vals):
if vals.get('email', False) and not self._context.get('do_not_update', False):
partner_record = self.get_partner(vals.get('email'))
vals.update({'related_partner_id': partner_record})
res = super(massMailingContact, self).write(vals)
return res
@api.depends('subscription_list_ids', 'subscription_list_ids.mailchimp_id', 'subscription_list_ids.list_id')
def _get_pending_for_export(self):
available_mailchimp_lists = self.env['mailchimp.lists'].search([])
lists = available_mailchimp_lists.mapped('odoo_list_id').ids
for record in self:
if record.subscription_list_ids.filtered(lambda x: x.list_id.id in lists and not x.mailchimp_id):
record.pending_for_export = True
else:
record.pending_for_export = False
@api.depends('email')
def _compute_is_email_valid(self):
for record in self:
record.is_email_valid = re.match(EMAIL_PATTERN, record.email)
@api.depends('email')
def _compute_related_partner_id(self):
for record in self:
query = """
SELECT id
FROM res_partner
WHERE LOWER(substring(email, '([^ ,;<@]+@[^> ,;]+)')) = LOWER(substring('{}', '([^ ,;<@]+@[^> ,;]+)'))""".format(record.email)
self._cr.execute(query)
partner_record = self._cr.fetchone()
if partner_record:
record.related_partner_id = partner_record[0]
else:
record.related_partner_id = False
is_email_valid = fields.Boolean(compute='_compute_is_email_valid', store=True)
pending_for_export = fields.Boolean(compute="_get_pending_for_export", string="Pending For Export", store=True)
related_partner_id = fields.Many2one('res.partner', 'Related Customer', compute="_compute_related_partner_id", help='Display related customer by matching Email address.', store=True)
subscription_list_ids = fields.One2many('mail.mass_mailing.list_contact_rel', 'contact_id', string='Subscription Information')
def _prepare_vals_for_merge_fields(self, mailchimp_list_id):
self.ensure_one()
merge_fields_vals = {}
partner_id = self.related_partner_id
for custom_field in mailchimp_list_id.merge_field_ids:
if custom_field.type == 'address' and partner_id:
address = {'addr1': partner_id.street or '',
'addr2': partner_id.street2 or '',
'city': partner_id.city or '',
'state': partner_id.state_id.name if partner_id.state_id else '',
'zip': partner_id.zip,
'country': partner_id.country_id.code if partner_id.country_id else ''}
merge_fields_vals.update({custom_field.tag: address})
elif custom_field.tag == 'FNAME':
merge_fields_vals.update({custom_field.tag: _partner_split_name(self.name)[0] if _partner_split_name(self.name)[0] else _partner_split_name(self.name)[1]})
elif custom_field.tag == 'LNAME':
merge_fields_vals.update({custom_field.tag: _partner_split_name(self.name)[1] if _partner_split_name(self.name)[0] else _partner_split_name(self.name)[0]})
elif custom_field.type in ['date', 'birthday']:
value = getattr(partner_id or self, custom_field.field_id.name) if custom_field.field_id and hasattr(partner_id or self, custom_field.field_id.name) else ''
if value:
value = datetime.strptime(value, DEFAULT_SERVER_DATETIME_FORMAT).strftime(custom_field.date_format)
# value = value.strftime(custom_field.date_format)
merge_fields_vals.update({custom_field.tag: value or ''})
else:
value = getattr(partner_id or self, custom_field.field_id.name) if custom_field.field_id and hasattr(partner_id or self, custom_field.field_id.name) else ''
merge_fields_vals.update({custom_field.tag: value or ''})
return merge_fields_vals
@api.multi
def action_export_to_mailchimp(self):
available_mailchimp_lists = self.env['mailchimp.lists'].search([])
lists = available_mailchimp_lists.mapped('odoo_list_id').ids
for record in self:
lists_to_export = record.subscription_list_ids.filtered(
lambda x: x.list_id.id in lists and not x.mailchimp_id)
for list in lists_to_export:
mailchimp_list_id = list.list_id.mailchimp_list_id
merge_fields_vals = record._prepare_vals_for_merge_fields(mailchimp_list_id)
# address = ''
# phone = ''
# partner_id = record.related_partner_id
# if partner_id:
# address = {'addr1': partner_id.street or '',
# 'addr2': partner_id.street2 or '',
# 'city': partner_id.city or '',
# 'state': partner_id.state_id.name if partner_id.state_id else '',
# 'zip': partner_id.zip,
# 'country': partner_id.country_id.code if partner_id.country_id else ''}
# phone = partner_id.phone or partner_id.mobile
prepared_vals = {"email_address": record.email.lower(),
"status": "unsubscribed" if record.opt_out else "subscribed",
"merge_fields": merge_fields_vals,
"tags": [tag.name for tag in record.tag_ids]}
response = mailchimp_list_id.account_id._send_request('lists/%s/members' % mailchimp_list_id.list_id,
prepared_vals, method='POST')
if response.get('web_id', False):
email_address = response.get('email_address')
md5_email = hashlib.md5(email_address.encode('utf-8')).hexdigest()
list.write({'mailchimp_id': response.get('web_id', False), 'md5_email': md5_email})
return True
@api.multi
def action_update_to_mailchimp(self):
available_mailchimp_lists = self.env['mailchimp.lists'].search([])
lists = available_mailchimp_lists.mapped('odoo_list_id').ids
for record in self:
lists_to_export = record.subscription_list_ids.filtered(
lambda x: x.list_id.id in lists and x.mailchimp_id)
for list in lists_to_export:
mailchimp_list_id = list.list_id.mailchimp_list_id
merge_fields_vals = record._prepare_vals_for_merge_fields(mailchimp_list_id)
# partner_id = record.related_partner_id
# address = ''
# phone = ''
# if partner_id:
# address = {'addr1': partner_id.street or '',
# 'addr2': partner_id.street2 or '',
# 'city': partner_id.city or '',
# 'state': partner_id.state_id.name if partner_id.state_id else '',
# 'zip': partner_id.zip,
# 'country': partner_id.country_id.code if partner_id.country_id else ''}
# phone = partner_id.phone or partner_id.mobile
prepared_vals = {"email_address": record.email.lower(),
"status": "unsubscribed" if record.opt_out else "subscribed",
'merge_fields': merge_fields_vals, }
response = mailchimp_list_id.account_id._send_request(
'lists/%s/members/%s' % (mailchimp_list_id.list_id, list.md5_email),
prepared_vals, method='PATCH')
tag_res = record.update_tag_on_mailchimp(response, mailchimp_list_id, list.md5_email)
if response.get('web_id', False):
email_address = response.get('email_address')
md5_email = hashlib.md5(email_address.encode('utf-8')).hexdigest()
list.write({'mailchimp_id': response.get('web_id', False), 'md5_email': md5_email})
return True
def update_tag_on_mailchimp(self, response, mailchimp_list_id, md5_email):
tag_list = []
tags = response.get('tags', []) and [tag['name'] for tag in response.get('tags', [])] or []
tag_name_list = self.tag_ids.mapped('name')
unique_tags = list(set(tags + tag_name_list))
for tag in unique_tags:
if tag in tag_name_list:
tag_dict = {'name': tag, 'status': 'active'}
else:
tag_dict = {'name': tag, 'status': 'inactive'}
tag_list.append(tag_dict)
tag_vals = {'tags': tag_list}
tag_res = mailchimp_list_id.account_id._send_request('lists/%s/members/%s/tags' % (mailchimp_list_id.list_id, md5_email), tag_vals, method='POST')
return tag_res
def fetch_specific_member_data(self, mailchimp_list_id, md5_email):
member_response = mailchimp_list_id.account_id._send_request('lists/%s/members/%s' % (mailchimp_list_id.list_id, md5_email), {}, method='GET')
tag_list = member_response.get('tags', [])
tag_ids = self.env['res.partner.category']
if tag_list:
tag_ids = self.env['res.partner.category'].create_or_update_tags(tag_list)
return tag_ids

View File

@ -0,0 +1,17 @@
from odoo import api, fields, models, _
class MassMailingList(models.Model):
_inherit = "mail.mass_mailing.list"
@api.one
def _compute_mailchimp_list_id(self):
mailchimp_list_obj = self.env['mailchimp.lists']
list_id = mailchimp_list_obj.search([('odoo_list_id', '=', self.id)])
if list_id:
self.mailchimp_list_id = list_id.id
else:
self.mailchimp_list_id = False
mailchimp_list_id = fields.Many2one('mailchimp.lists', compute='_compute_mailchimp_list_id',
string="Associated MailChimp List")

View File

@ -0,0 +1,37 @@
from odoo import api, fields, models, _
class MassMailingContactListRel(models.Model):
""" Intermediate model between mass mailing list and mass mailing contact
Indicates if a contact is opted out for a particular list
"""
_name = 'mail.mass_mailing.list_contact_rel'
_description = 'Mass Mailing Subscription Information'
_table = 'mail_mass_mailing_contact_list_rel'
_rec_name = 'contact_id'
@api.depends('list_id')
def _compute_mailchimp_list_id(self):
mailchimp_list_obj = self.env['mailchimp.lists']
for record in self:
list_id = mailchimp_list_obj.search([('odoo_list_id', '=', record.list_id.id)], limit=1)
record.mailchimp_list_id = list_id.id
contact_id = fields.Many2one('mail.mass_mailing.contact', string='Contact', ondelete='cascade', required=True)
list_id = fields.Many2one('mail.mass_mailing.list', string='Mailing List', ondelete='cascade', required=True)
mailchimp_id = fields.Char("MailChimp ID", readonly=1, copy=False)
mailchimp_list_id = fields.Many2one(compute="_compute_mailchimp_list_id", string="MailChimp List", store=True)
md5_email = fields.Char("MD5 Email", readonly=1, copy=False)
related_partner_id = fields.Many2one('res.partner', related='contact_id.related_partner_id', string='Related Customer', readonly=True, store=True)
opt_out = fields.Boolean(related="contact_id.opt_out", string='Opt Out', help='The contact has chosen not to receive mails anymore from this list')
_sql_constraints = [
('unique_contact_list', 'unique (contact_id, list_id)',
'A contact cannot be subscribed multiple times to the same list!')
]
@api.model_cr
def init(self):
self._cr.execute("""select * from information_schema.columns where column_name = 'id' and table_name = 'mail_mass_mailing_contact_list_rel'""")
if not self._cr.fetchone():
self._cr.execute("""ALTER TABLE mail_mass_mailing_contact_list_rel ADD COLUMN id SERIAL PRIMARY KEY""")

View File

@ -0,0 +1,40 @@
from odoo import fields, models, api
class ResPartner(models.Model):
_inherit = 'res.partner'
subscription_list_ids = fields.One2many('mail.mass_mailing.list_contact_rel',
'related_partner_id', string='Subscription Information', domain=[('mailchimp_list_id','!=',False)])
@api.multi
def get_mailing_partner(self, email):
query = """
SELECT id
FROM mail_mass_mailing_contact
WHERE LOWER(substring(email, '([^ ,;<@]+@[^> ,;]+)')) = LOWER(substring('{}', '([^ ,;<@]+@[^> ,;]+)'))""".format(email)
self._cr.execute(query)
return self._cr.fetchone()
@api.model
def create(self, vals):
res = super(ResPartner, self).create(vals)
if vals.get('email', False):
mailing_contact = self.env['mail.mass_mailing.contact']
partner_record = self.get_mailing_partner(vals.get('email'))
if partner_record:
mailing_contact.browse(partner_record[0]).related_partner_id = res.id
return res
@api.multi
def write(self, vals):
if vals.get('email', False):
mailing_contact = self.env['mail.mass_mailing.contact']
partner_record = self.get_mailing_partner(vals.get('email'))
if partner_record:
mailing_contact.browse(partner_record[0]).related_partner_id = self.id
else:
partner_record = self.get_mailing_partner(self.email)
if partner_record:
mailing_contact.browse(partner_record[0]).write({'related_partner_id':False})
res = super(ResPartner, self).write(vals)
return res

View File

@ -0,0 +1,24 @@
from odoo import fields,api,models
class ResPartnerCategory(models.Model):
_inherit = 'res.partner.category'
mailchimp_id = fields.Char('Mailchimp Id')
@api.multi
def create_or_update_tags(self, values_dict, account=False):
tag_ids = self
for val in values_dict:
tag_id = val.get('id')
existing_list = self.search([('mailchimp_id', '=', tag_id)])
val.update({'mailchimp_id':val.pop('id')})
if not existing_list:
existing_list =self.search([('name', '=', val.get('name'))])
if not existing_list:
existing_list = self.create(val)
else:
existing_list.write(val)
else:
existing_list.write(val)
tag_ids += existing_list
return tag_ids

View File

@ -0,0 +1,13 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mailchimp_accounts_public,mailchimp.accounts,mailchimp.model_mailchimp_accounts,,1,0,0,0
access_mailchimp_lists_public,mailchimp.lists,mailchimp.model_mailchimp_lists,,1,0,0,0
access_mailchimp_lists_stats_public,mailchimp.lists.stats,mailchimp.model_mailchimp_lists_stats,,1,0,0,0
access_mailchimp_templates_public,mailchimp.templates,mailchimp.model_mailchimp_templates,,1,0,0,0
access_mailchimp_accounts_user,mailchimp.accounts.user,mailchimp.model_mailchimp_accounts,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailchimp_lists_user,mailchimp.lists.user,mailchimp.model_mailchimp_lists,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailchimp_lists_stats_user,mailchimp.lists.stats.user,mailchimp.model_mailchimp_lists_stats,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailchimp_templates_user,mailchimp.templates.user,mailchimp.model_mailchimp_templates,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailchimp_segments_public,mailchimp.segments,mailchimp.model_mailchimp_segments,,1,0,0,0
access_mailchimp_segments_user,mailchimp.segments.user,mailchimp.model_mailchimp_segments,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailchimp_merge_fields_public,mailchimp.merge.fields,mailchimp.model_mailchimp_merge_fields,,1,0,0,0
access_mailchimp_merge_fields_user,mailchimp.merge.fields.user,mailchimp.model_mailchimp_merge_fields,mass_mailing.group_mass_mailing_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mailchimp_accounts_public mailchimp.accounts mailchimp.model_mailchimp_accounts 1 0 0 0
3 access_mailchimp_lists_public mailchimp.lists mailchimp.model_mailchimp_lists 1 0 0 0
4 access_mailchimp_lists_stats_public mailchimp.lists.stats mailchimp.model_mailchimp_lists_stats 1 0 0 0
5 access_mailchimp_templates_public mailchimp.templates mailchimp.model_mailchimp_templates 1 0 0 0
6 access_mailchimp_accounts_user mailchimp.accounts.user mailchimp.model_mailchimp_accounts mass_mailing.group_mass_mailing_user 1 1 1 1
7 access_mailchimp_lists_user mailchimp.lists.user mailchimp.model_mailchimp_lists mass_mailing.group_mass_mailing_user 1 1 1 1
8 access_mailchimp_lists_stats_user mailchimp.lists.stats.user mailchimp.model_mailchimp_lists_stats mass_mailing.group_mass_mailing_user 1 1 1 1
9 access_mailchimp_templates_user mailchimp.templates.user mailchimp.model_mailchimp_templates mass_mailing.group_mass_mailing_user 1 1 1 1
10 access_mailchimp_segments_public mailchimp.segments mailchimp.model_mailchimp_segments 1 0 0 0
11 access_mailchimp_segments_user mailchimp.segments.user mailchimp.model_mailchimp_segments mass_mailing.group_mass_mailing_user 1 1 1 1
12 access_mailchimp_merge_fields_public mailchimp.merge.fields mailchimp.model_mailchimp_merge_fields 1 0 0 0
13 access_mailchimp_merge_fields_user mailchimp.merge.fields.user mailchimp.model_mailchimp_merge_fields mass_mailing.group_mass_mailing_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,451 @@
<section class="oe_container">
<div class="mt64 mb64 pt32">
<h3 class="text-center alert"
style="font-family: 'Lato'0, 'Open Sans', 'Helvetica', Sans;font-size: 40px;font-weight: 300;color:#875A7B;">
MailChimp Odoo Integration</h3>
<p class="oe_mt32"
style="font-size: 20px; font-weight: 300;line-height: 150% !important; font-family: 'Lato', 'Open Sans', 'Helvetica', Sans !important; text-align:center;">
Seamless one click integration to synchronize your contact list, campaign, templates between Odoo and
MailChimp. Level up your email marketing, get more sales and happy customers.
</p>
</div>
</section>
<section>
<div class="text-center">
<div class="mt-5 mb-4">
<a class="btn btn-primary"
style="width: auto;font-weight: bold;font-size: 21px;background: linear-gradient(to right, #61455a, #bc088b);box-shadow: 0px 0px 6px 3px #d7e5f1;padding-top: 12px;padding-bottom: 12px;padding-left: 40px;padding-right: 40px;border-radius: 10px; color: white; border: none;"
href="mailto:info@teqstars.com" target="_blank">Request A Demo
</a>
</div>
</div>
</section>
<section>
<div class="text-center">
<div class="s_panel_video" data-video-id="DMnydaAzDGc?rel=0" style="cursor:pointer;">
<img class="s_tooltip_tabs_tooltip_image s_figure_link img-fluid pb0"
src="mailchimp_video.jpg" alt="MailChimp Tutorial" style="max-width:60%;">
</div>
</div>
</section>
<section class="oe_container">
<div id="loempia_tabs" class=""
style="padding:64px 0px;margin: 100px 0px;background-color:#f6f7f9;border-radius: 10px;">
<ul role="tablist" class="nav nav-tabs justify-content-center" data-tabs="tabs" style="
border: none;
background: unset;
">
<li class="nav-item"
style="border-top-right-radius: 6px;border-top-left-radius: 6px;background: linear-gradient(to top, #FFD14F, #ffdc7a);margin-right: 1px;padding-bottom: 2px;">
<a href="#features" data-toggle="tab" aria-expanded="true" class="active"
style="font-family: Roboto;text-transform: uppercase;font-weight: 600;font-size: 15px;letter-spacing: 1px;padding: 10px 20px;border-top-left-radius: 5px;border-top-right-radius: 5px;color:#000000;">Features
</a>
</li>
<li class="nav-item"
style="border-top-right-radius: 6px;border-top-left-radius: 6px;background: linear-gradient(to top, #FFD14F, #ffdc7a);margin-right: 1px;padding-bottom: 2px;">
<a href="#screenshots" data-toggle="tab" aria-expanded="true" class=""
style="font-family: Roboto;text-transform: uppercase;font-weight: 600;font-size: 15px;letter-spacing: 1px;padding: 10px 20px;border-top-left-radius: 5px;border-top-right-radius: 5px;color:#000000;">Screenshots
</a>
</li>
<li class="nav-item"
style="border-top-right-radius: 6px;border-top-left-radius: 6px;background: linear-gradient(to top, #FFD14F, #ffdc7a);margin-right: 1px;padding-bottom: 2px;">
<a href="#changelogs" data-toggle="tab" aria-expanded="true" class=""
style="font-family: Roboto;text-transform: uppercase;font-weight: 600;font-size: 15px;letter-spacing: 1px;padding: 10px 20px;border-top-left-radius: 5px;border-top-right-radius: 5px;color:#000000;">Changelogs
</a>
</li>
<li class="nav-item"
style="border-top-right-radius: 6px;border-top-left-radius: 6px;background: linear-gradient(to top, #FFD14F, #ffdc7a);margin-right: 1px;padding-bottom: 2px;">
<a href="#faqs" data-toggle="tab" aria-expanded="true" class=""
style="font-family: Roboto;text-transform: uppercase;font-weight: 600;font-size: 15px;letter-spacing: 1px;padding: 10px 20px;border-top-left-radius: 5px;border-top-right-radius: 5px;color:#000000;">FAQs
</a>
</li>
</ul>
<div id="tabs_content" class="tab-content"
style="background: #fff;border-radius: 10px;padding: 2%;margin: 0% 2%;padding-top:4%;">
<!-- Features Tabs -->
<div class="tab-pane active show" id="features">
<div class="container">
<section id="other-legal-references">
<ul style="margin-bottom:8px !important;font-size: 20px;line-height: 200% !important; font-weight: 300; font-family: 'Lato', 'Open Sans', 'Helvetica', Sans;">
<li>
Import <b>Lists/Audiences</b> from MailChimp to Odoo.
</li>
<li>
Import <b>Member/contacts</b> from MailChimp to Odoo.
</li>
<li>
Import <b>Templates</b> from MailChimp to Odoo.
</li>
<li>
Import <b>Campaigns</b> from MailChimp to Odoo.
</li>
<li>
Import <b>Campaign Reports</b> from MailChimp to Odoo.
</li>
<li>
Import <b>Merge Fields</b> from MailChimp to Odoo and map it with Odoo fields.
</li>
<li>
Import <b>Segments</b> from MailChimp to Odoo and that will use to filter recipients while sending Campaign
</li>
<li>
Export/Update <b>Lists/Audiences</b> from Odoo to MailChimp
</li>
<li>
Export/Update <b>Member/contacts</b> from Odoo to MailChimp
</li>
<li>
<b>Create Campaign</b> by using MailChimp Template applying recipients filter using Segments and send it to MailChimp
</li>
<li>
Update record on the fly by setting up <b>Webhook</b>
</li>
<li>
Easy to get <b>Statistics</b> of Campaigns and Audiences.
</li>
<li>
Use <b>Multiple Accounts</b> as well.
</li>
</ul>
</section>
</div>
</div>
<!-- ScreenShots Tabs -->
<div class="tab-pane" id="screenshots">
<section class="oe_container">
<div class="tab__content">
<h2 class="oe_slogan" style="color:#875A7B;">Setup MailChimp account</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/1.png">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">Configure auto member synchronization</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/3.png">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">View available Lists and Campaigns from
Account</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/2.png">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">MailChimp Lists/Audiences</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/4.gif">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">Setting up or change any setting directly in Odoo
and Update it to MailChimp.</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/5.png">
</div>
<div class="oe_mt32">
<img class="img img-responsive" src="img/6.png">
</div>
<div class="oe_mt32">
<img class="img img-responsive" src="img/7.png">
</div>
<div class="oe_mt32">
<img class="img img-responsive" src="img/8.png">
</div>
<div class="oe_mt32">
<img class="img img-responsive" src="img/9.png">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">MailChimp Template</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/11.png">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">Do multiple operation on one click</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/12.png">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">Easy to identify MailChimp list from other
lists</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/13.png">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">Send Campaign with filtering recipients by selecting segments.</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/5.gif">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">Send Campaign by creating mass mailing record and
get statistics back to Odoo</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/14.png">
</div>
<div class="oe_mt32">
<img class="img img-responsive" src="img/15.1.png">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">Quick shoot of export and update process</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/17.gif">
</div>
</section>
<h2 class="oe_slogan" style="color:#875A7B;">Setup Webhook to get real-time information</h2>
<section class="section">
<div class="oe_mt32">
<img class="img img-responsive" src="img/16.png">
</div>
</section>
</div>
</section>
</div>
<div class="tab-pane" id="changelogs" style="margin-bottom:8px !important;font-size: 20px;line-height: 150% !important; font-weight: 300; font-family: 'Lato', 'Open Sans', 'Helvetica', Sans;">
<section class="section" style="padding: 3rem 1.5rem;">
<div class="container is-narrow" style="margin: 0 auto;position: relative;max-width: 900px;">
<div class="content"
style="font-size: medium;font-weight: 300;font-family: 'Lato', 'Open Sans', 'Helvetica', Sans;">
<p>
<h2 style="margin-top: 1.1428em;text-align: left;display: inline;color: #363636;font-weight: 400;line-height: 1.125;font-size: 1.75em;margin-bottom: .5714em;color: #4608ad;font-weight: 300;">
v11.0.3.0 </h2> Released on 17 October 2019 (UTC).
<div class="changelog">
<div class="level" style="align-items: center;justify-content: space-between;">
<div class="level-left"
style="flex-basis: auto; flex-grow: 0; flex-shrink: 0; align-items: center; justify-content: flex-start;">
<span class="tag"
style="font-family: monospace; margin: 0 .5rem; font-size: medium; font-weight: 300;align-items: center; border-radius: 3px;display: inline-flex; font-size: .75rem; height: 2em; justify-content: center; line-height: 1.5; padding-left: .75em; padding-right: .75em; white-space: nowrap; background-color: #6BBE45;color: #fff;">ADD</span>
Allow to Import <b> Merge field (Custom fields)</b> and map it with Odoo field. So, user can configure n no of custom fields to export or update to MailChimp.
</div>
</div>
</div>
<br>
<h2 style="margin-top: 1.1428em;text-align: left;display: inline;color: #363636;font-weight: 400;line-height: 1.125;font-size: 1.75em;margin-bottom: .5714em;color: #4608ad;font-weight: 300;">
v11.0.2.0 </h2> Released on 16 October 2019 (UTC).
<div class="changelog">
<div class="level" style="align-items: center;justify-content: space-between;">
<div class="level-left"
style="flex-basis: auto; flex-grow: 0; flex-shrink: 0; align-items: center; justify-content: flex-start;">
<span class="tag update_fix"
style="font-family: monospace; margin: 0 .5rem; font-size: medium; font-weight: 300;align-items: center; border-radius: 3px;display: inline-flex; font-size: .75rem; height: 2em; justify-content: center; line-height: 1.5; padding-left: .75em; padding-right: .75em; white-space: nowrap; background-color: #E31329;color: #fff;">FIX</span>
Minor bugs and increase speed of import/fetch member process.
</div>
</div>
<div class="level" style="align-items: center;justify-content: space-between;">
<div class="level-left"
style="flex-basis: auto; flex-grow: 0; flex-shrink: 0; align-items: center; justify-content: flex-start;">
<span class="tag"
style="font-family: monospace; margin: 0 .5rem; font-size: medium; font-weight: 300;align-items: center; border-radius: 3px;display: inline-flex; font-size: .75rem; height: 2em; justify-content: center; line-height: 1.5; padding-left: .75em; padding-right: .75em; white-space: nowrap; background-color: #6BBE45;color: #fff;">ADD</span>
Introduced <b>Segments</b> to filter recipients while sending campaign.
</div>
</div>
</div>
<br>
</div>
</div>
</section>
</div>
<!-- FAQs Section -->
<div class="tab-pane" id="faqs">
<section class="oe_container">
<div class="s_faq mt32 mb32" style="background-color: transparent !important;">
<div class="tab__content">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" style="color:#A5187E" data-parent="#accordion"
href="#collapse2">
Whom to contact for Customizations?</a>
</h4>
</div>
<div id="collapse2" class="panel-collapse collapse">
<div class="panel-body">You can drop mail at <a href="mailto:info@teqstars.com">info@teqstars.com</a>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" style="color:#A5187E" data-parent="#accordion"
href="#collapse3">
Do you provide any support?</a>
</h4>
</div>
<div id="collapse3" class="panel-collapse collapse">
<div class="panel-body">Yes, We provide 90 days free support for bugs.
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" style="color:#A5187E" data-parent="#accordion"
href="#collapse4">
Does your app support Enterprise Version?</a>
</h4>
</div>
<div id="collapse4" class="panel-collapse collapse">
<div class="panel-body">Yes.
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</section>
<!-- The slideshow -->
<div id="demo" class="row carousel slide mt64 mb32" data-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active" style="min-height: 0px;">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left;">
<a href="https://www.odoo.com/apps/modules/12.0/fedex_delivery/" target="_blank">
<div style="box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);border-radius: 10px;">
<img class="img img-responsive center-block"
style="border-radius:10px;"
src="fedex_odoo.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left;">
<a href="https://apps.odoo.com/apps/modules/12.0/nearby_warehouse/" target="_blank">
<div style="box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);border-radius: 10px;">
<img class="img img-responsive center-block"
style="border-radius:10px;"
src="nearby_warehouse.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left;">
<a href="https://apps.odoo.com/apps/modules/12.0/shipstation_delivery/" target="_blank">
<div style="box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);border-radius: 10px;">
<img class="img img-responsive center-block"
style="border-radius:10px;"
src="shipstation_odoo.png">
</div>
</a>
</div>
</div>
<div class="carousel-item" style="min-height: 0px;">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left;">
<a href="https://www.odoo.com/apps/modules/12.0/web_sticky_list_header/" target="_blank">
<div style="box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);border-radius: 10px;">
<img class="img img-responsive center-block"
style="border-radius:10px;"
src="sticky_header.jpg">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left;">
<a href="https://apps.odoo.com/apps/modules/12.0/ts_dashboard/" target="_blank">
<div style="box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);border-radius: 10px;">
<img class="img img-responsive center-block"
style="border-radius:10px;"
src="all_in_one_dashboard.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left;">
<a href="https://www.odoo.com/apps/modules/11.0/product_alias/" target="_blank">
<div style="box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);border-radius: 10px;">
<img class="img img-responsive center-block"
style="border-radius:10px;"
src="product_alias.png">
</div>
</a>
</div>
</div>
</div>
<!-- Left and right controls -->
<a class="carousel-control-prev" href="#demo" data-slide="prev"
style="left:-25px;width: 35px;color: #000;">
<span class="carousel-control-prev-icon"><i class="fa fa-chevron-left"
style="font-size:24px"></i></span>
</a>
<a class="carousel-control-next" href="#demo" data-slide="next"
style="right:-25px;width: 35px;color: #000;">
<span class="carousel-control-next-icon"><i class="fa fa-chevron-right"
style="font-size:24px"></i></span>
</a>
</div>
<div style="
border: 0;
height: 4px;
border-top: #ddd 1px solid;
border-bottom: #ddd 1px solid;
text-align: center;
position: relative;
clear: both;"><i class="fa fa-star fa-2x" style="
color: #bbb;
text-align: center;
display: inline-block;
height: 50px;
line-height: 50px;
text-align: center;
width: 50px;
font-size: 20px;
position: absolute;
top: -25px;
left: 50%;
margin: 0 auto 0 -25px;
z-index:999"></i></div>
<section class="oe_container oe_dark">
<div class="oe_row">
<div class="oe_span12">
<center>
<a href="http://teqstars.com" target="_blank">
<img class="oe_picture" src="img/TeqStars.png" style="height:128px;weight:128px;"/>
</a>
</center>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_mt16 oe_mb16">
<h2 class="oe_slogan" style="color:#875A7B;margin-top: 15px;">Need Any Help?</h2>
<h3 class="oe_slogan" style="font-variant: initial;">Contact us for any problem or request!</h3>
<div class="separator"
style="display: block;width: 60px;height: 5px;margin: 15px auto 15px;background-color: #d3b61c;position: relative; border: 1px solid #d3b61c;"></div>
</div>
<div class="oe_slogan">
<a class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;" href="mailto:info@teqstars.com"><i
class="fa fa-envelope"></i> Want Demo or Need Support? </a>
</div>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -0,0 +1,20 @@
.o_kanban_view {
.oe_kanban_mailchimp {
.o_title {
margin-bottom: 16px;
}
.o_kanban_primary_bottom {
margin-top: 16px;
}
.oe_margin_top_8 {
margin-top: 8px;
}
.oe_margin_bottom_8 {
margin-bottom: 8px;
}
}
}
.o_white_body {
background-color: white;
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="mailchimp assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/mailchimp/static/src/scss/mailchimp.scss"/>
</xpath>
</template>
</odoo>

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_mailchimp_accounts_form" model="ir.ui.view">
<field name="name">mailchimp.accounts.form</field>
<field name="model">mailchimp.accounts</field>
<field name="arch" type="xml">
<form string="MailChimp Accounts">
<header>
<button name="test_connection" string="Test Connection"
type="object" class="btn-success"/>
</header>
<sheet>
<div class="oe_title">
<label class="oe_edit_only" for="name" string="Account Name"/>
<h1>
<field name="name" placeholder="Account Name"/>
</h1>
</div>
<group>
<group string="Authentication">
<field name="api_key"/>
</group>
</group>
<group string='MailChimp Tutorial'
attrs="{'invisible': [('api_key', '!=', False)]}">
<ul>
<li>
<b>
Go to
<a href='https://mailchimp.com/help/about-api-keys/' target='_blank'>MailChimp
Website
</a>
to create or retrive API Key
</b>
</li>
</ul>
</group>
<notebook>
<page name="lists" string="Lists">
<field name="list_ids" nolabel="1">
<tree string="MailChimp Lists/Audiences" create="false">
<field name="name"/>
<field name="date_created"/>
<field name="list_id"/>
<field name="partner_id"/>
<field name="list_rating"/>
<!--<button name="export_in_mailchimp" attrs="{'invisible': [('list_id','!=', False)]}" string="Export In MailChimp" type="object" icon="fa-external-link"/>-->
<!--<button name="update_in_mailchimp" attrs="{'invisible': [('list_id','=', False)]}" string="Update In MailChimp" type="object" icon="fa-share-square-o"/>-->
<button name="refresh_list" attrs="{'invisible': [('list_id','=', False)]}"
string="Refresh" type="object" icon="fa-refresh"/>
</tree>
</field>
</page>
<page name="campaign" string="Campaigns">
<field name="campaign_ids" nolabel="1" readonly="1"/>
</page>
<page name="settings" string="Settings">
<h2>Member Options</h2>
<div class="row mt16 o_settings_container" id="member_option">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="auto_create_member"/>
</div>
<div class="o_setting_right_pane">
<label for="auto_create_member" string="Auto Create Member In Odoo?"/>
<div class="text-muted">
While syncing member lists would you like to create new mailling contact
in Odoo if not found in Odoo?
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="auto_refresh_member"/>
</div>
<div class="o_setting_right_pane">
<label for="auto_refresh_member"/>
<div class="text-muted">
Auto Import/Update member in Odoo at defined in scheduled action.
</div>
<div attrs="{'invisible': [('auto_refresh_member','=',False)]}"
class="mt16">
<p attrs="{'invisible': [('auto_refresh_member', '=', False)]}">
<button name="get_refresh_member_action" icon="fa-arrow-right" type="object" string="Scheduled Actions" class="btn-link"/>
</p>
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box" attrs="{'invisible': [('auto_create_member', '=', False)]}">
<div class="o_setting_left_pane">
<field name="auto_create_partner"/>
</div>
<div class="o_setting_right_pane">
<label for="auto_create_partner" string="Auto Create Customers In Odoo?"/>
<div class="text-muted">
While syncing member lists would you like to create new Customers(Odoo Contacts)
in Odoo if not found in Odoo?
</div>
</div>
</div>
</div>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_mailchimp_accounts_tree" model="ir.ui.view">
<field name="name">mailchimp.accounts.tree</field>
<field name="model">mailchimp.accounts</field>
<field name="arch" type="xml">
<tree string="MailChimp Account">
<field name="name"/>
<field name="api_key"/>
</tree>
</field>
</record>
<record id="action_mailchimp_accounts" model="ir.actions.act_window">
<field name="name">MailChimp Accounts</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mailchimp.accounts</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context"></field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new MailChimp Account.
</p>
<p>
Accounts allows you to manage the all MailChimp operation from here.
</p>
</field>
</record>
<menuitem id="menu_mailchimp_root" name="MailChimp" parent="mass_mailing.mass_mailing_menu_root"
sequence="3" web_icon="mailchimp,static/description/icon.png" groups="mass_mailing.group_mass_mailing_user"/>
<menuitem name="Accounts" parent="menu_mailchimp_root"
action="action_mailchimp_accounts" id="menu_action_mailchimp_accounts"
sequence="10"/>
</data>
</odoo>

View File

@ -0,0 +1,429 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_mailchimp_lists_form" model="ir.ui.view">
<field name="name">mailchimp.lists.form</field>
<field name="model">mailchimp.lists</field>
<field name="arch" type="xml">
<form string="MailChimp Lists/Audiences">
<header>
<button name="export_in_mailchimp" string="Export In MailChimp" type="object"
class="btn-success" attrs="{'invisible' :[('list_id','!=', False)]}"/>
<button name="update_in_mailchimp" string="Update In MailChimp" type="object"
class="btn-success" attrs="{'invisible' :[('list_id','=', False)]}"/>
<button name="refresh_list" string="Refresh" type="object" class="btn-primary"
attrs="{'invisible' :[('list_id','=', False)]}"/>
<button name="fetch_merge_fields" string="Fetch Merge Fields" type="object" class="btn-primary"
attrs="{'invisible' :[('list_id','=', False)]}"/>
<button name="fetch_segments" string="Fetch Segments" type="object" class="btn-primary"
attrs="{'invisible' :[('list_id','=', False)]}"/>
<button name="fetch_members" string="Fetch Members" type="object" class="btn-primary"
attrs="{'invisible' :[('list_id','=', False)]}"/>
</header>
<!--
<div class="alert alert-warning" role="alert"
style="margin-bottom:0px;"
attrs="{'invisible': [('is_update_required','=',False)]}">
<i class="fa fa-exclamation-triangle text-warning"></i>
Some of the data is changed since last sync. Update to <bold>MailChimp</bold> as well or refresh
list if you want to discard changes.
</div> -->
<sheet>
<div class="oe_button_box">
<button name="action_view_recipients" context="{'show_total': True}"
type="object" icon="fa-user" class="oe_stat_button">
<field name="contact_total_nbr" string="Total Contacts" widget="statinfo"/>
</button>
<button name="action_view_recipients" context="{'show_sub': True}"
type="object" icon="fa-user text-success" class="oe_stat_button">
<field name="contact_nbr" string="Subscribers" widget="statinfo"/>
</button>
<button name="action_view_recipients" context="{'show_unsub': True}"
type="object" icon="fa-user text-info" class="oe_stat_button">
<field name="contact_unsub_nbr" string="Unsubscribed Contacts" widget="statinfo"/>
</button>
<button name="action_view_recipients" context="{'show_cleaned': True}"
type="object" icon="fa-user text-danger" class="oe_stat_button">
<field name="contact_cleaned_nbr" string="Cleaned Contacts" widget="statinfo"/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Audience Name"/>
<h1>
<field name="name" placeholder="Audience Name"/>
</h1>
<div name="options">
<div>
<label for="account_id" string="Associated Account"/>
<field name="account_id" required="1"/>
</div>
</div>
</div>
<group>
<group>
<field name="date_created"/>
<field name="subscribe_url_short"/>
<field name="subscribe_url_long"/>
</group>
<group>
<field name="list_rating" widget="priority" readonly="1"/>
<field name="member_since_last_changed"/>
<field name="list_id" invisible="1"/>
<field name="is_update_required" invisible="1"/>
</group>
</group>
<notebook>
<page string="Settings" name="settings">
<h2>Publicity settings</h2>
<div class="row mt16 o_settings_container" id="new_sub">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="visibility" string="Promote my campaigns"/>
<div class="text-muted">
Mailchimp builds tools and services that help people discover
newsletters and campaigns.
When we build these tools, would you like to be discovered?
</div>
<div>
<field name="visibility" widget="radio" class="mt16"/>
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="use_archive_bar"/>
</div>
<div class="o_setting_right_pane">
<label for="use_archive_bar" string="Archive bar"/>
<div class="text-muted">
When subscribers click the “view in browser” archive link in your email,
we'll display the archived version of your campaign in their browser,
along with a toolbar that lets them view past campaigns and share your
emails on social networks..
</div>
</div>
</div>
</div>
<br/>
<h2>Form settings</h2>
<div class="row mt16 o_settings_container" id="form_settings">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="double_optin"/>
</div>
<div class="o_setting_right_pane">
<label for="double_optin"/>
<div class="text-muted">
Send contacts an opt-in confirmation email when they subscribe to your
audience.
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="marketing_permissions"/>
</div>
<div class="o_setting_right_pane">
<label for="marketing_permissions"/>
<div class="text-muted">
Customize your forms to include GDPR fields.
</div>
</div>
</div>
</div>
<br/>
<h2>Campaign defaults</h2>
<div class="row mt16 o_settings_container" id="form_settings">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="from_name"/>
<div class="text-muted">
This is the name your emails will come from. Use something your
subscribers will instantly recognize, like your company name.
</div>
<div>
<field name="from_name" class="mt16"/>
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="email_type_option"/>
</div>
<div class="o_setting_right_pane">
<label for="email_type_option"/>
<div class="text-muted">
When people sign up for your audience, you can let them specify which
email format they prefer to receive. If they choose “Plain-text”, then
they won't receive your fancy HTML version.
</div>
</div>
</div>
<!-- commented because we aren't getting this field value from API
<div class="col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="has_welcome"/>
</div>
<div class="o_setting_right_pane">
<label for="has_welcome"/>
<div class="text-muted">
When people opt-in to your audience, send them an email welcoming them
to your audience. The final welcome email can be edited in the audience
forms designer.
</div>
</div>
</div> -->
</div>
<div class="row mt16 o_settings_container" id="form_settings">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="from_email"/>
<div class="text-muted">
The entered address receives reply emails. Check it regularly to stay in
touch with your audience.
</div>
<div>
<field name="from_email" class="mt16"/>
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="lang_id"/>
<div class="text-muted">
Default Language for Campaign
</div>
<div>
<field name="lang_id" required="1" class="mt16"/>
</div>
</div>
</div>
</div>
<div class="row mt16 o_settings_container" id="form_settings">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="subject"/>
<div class="text-muted">
Keep it relevant and non-spammy.
</div>
<div>
<field name="subject" class="mt16"/>
</div>
</div>
</div>
</div>
<br/>
<h2>New subscriber notifications</h2>
<h3>One by one</h3>
<div class="text-muted">
Get quick email alerts when subscribers join or leave this audience.
</div>
<div class="row mt16 o_settings_container" id="new_sub">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="notify_on_subscribe"/>
<div class="text-muted">
Additional email addresses must be separated by a comma.
</div>
<div>
<field name="notify_on_subscribe" class="mt16"/>
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="notify_on_unsubscribe"/>
<div class="text-muted">
Additional email addresses must be separated by a comma.
</div>
<div>
<field name="notify_on_unsubscribe" class="mt16"/>
</div>
</div>
</div>
</div>
<br/>
<h2>Required Email Footer Content</h2>
<div class="row mt16 o_settings_container" id="new_sub">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="permission_reminder" string="Permission reminder"/>
<div class="text-muted">
Sometimes people forget they signed up for an email newsletter. To
prevent false spam reports, its best to briefly remind your recipients
how they got on your list.
MailChimpll automatically place this in your Mailchimp templates
wherever you see *|LIST:DESCRIPTION|*. About permission reminders.
</div>
<div>
<field name="permission_reminder" class="mt16"/>
</div>
</div>
</div>
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="notify_on_unsubscribe"
string="How can recipients contact you?"/>
<div class="text-muted">
Enter the contact information and physical mailing address for the owner
of this list. This is required by law. If youre an agency sending on
behalf of a client, enter your clients information.
</div>
<div>
<field name="partner_id" required="1" class="mt16"/>
</div>
</div>
</div>
</div>
<br/>
<h2>Email Beamer</h2>
<div class="row mt16 o_settings_container" id="new_sub">
<div class="col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="beamer_address" string="Permission reminder"/>
<div class="text-muted">
Every list you create in Mailchimp gets its own special email address.
Send to this private email address from your favorite email client, and
well save those emails as draft campaigns in your account. From there,
you can send the campaign to your Mailchimp list.
Send an email to this private address, and MailChimpll reply with a
confirmation link:
</div>
<div>
<field name="beamer_address" class="mt16"/>
</div>
</div>
</div>
</div>
</page>
<page string="Segments" name="segments">
<field name="segment_ids" nolabel='1'>
<tree editable="top" delete="false" create="false">
<field name="name"/>
</tree>
</field>
</page>
<page string="Merge Fields" name="merge_fields">
<div class="alert alert-info info_icon" role="alert">
<span class="fa fa-lightbulb-o fa-lg"><strong> Notes</strong></span>
<p>
<br/> 1. Make sure you have "FNAME" for First Name and "LNAME" for Last Name in merge field tag.
<br/> 2. You don't need to select Odoo Field for Address Type of field and for FNAME and LNAME tag because it will get selected automatically at the time of exporting contact or updating contact.
</p>
</div>
<field name="merge_field_ids" nolabel='1'>
<tree editable="top" delete="false" create="false">
<field name="name" readonly="1"/>
<field name="type" readonly="1"/>
<field name="required" readonly="1"/>
<field name="public" readonly="1"/>
<field name="tag" readonly="1"/>
<field name="default_value" readonly="1"/>
<field name="field_id" attrs="{'readonly': ['|', ('type','=', 'address'), ('tag','in', ['FNAME','LNAME'])]}"/>
<field name="list_id" invisible="1"/>
<field name="merge_id" invisible="1"/>
</tree>
</field>
</page>
<page string="Statistics" name="settings">
<group string="Overview">
<field name="stats_overview_ids" readonly="1" nolabel='1'>
<tree editable="bottom" delete="false" create="false" edit="false">
<field name="member_count"/>
<field name="unsubscribe_count"/>
<field name="cleaned_count"/>
<field name="last_sub_date"/>
<field name="last_unsub_date"/>
<field name="campaign_count"/>
<field name="campaign_last_sent"/>
</tree>
</field>
</group>
<group col="3">
<group string="Audience performance">
<field name="stats_audience_perf_ids" readonly="1" nolabel='1'>
<tree editable="bottom" delete="false" create="false" edit="false">
<field name="avg_sub_rate" string="Avg Sub %"/>
<field name="avg_unsub_rate" string="Avg Unsub %"/>
<field name="target_sub_rate" string="Target Sub %"/>
</tree>
</field>
</group>
<group string="Since Last Campaign">
<field name="stats_since_last_campaign_ids" readonly="1" nolabel='1'>
<tree editable="bottom" delete="false" create="false" edit="false">
<field name="member_count_since_send"/>
<field name="unsubscribe_count_since_send"/>
<field name="cleaned_count_since_send"/>
</tree>
</field>
</group>
<group string="Campaign Performance">
<field name="stats_campaign_perf_ids" readonly="1" nolabel='1'>
<tree editable="bottom" delete="false" create="false" edit="false">
<field name="open_rate" string="Open %"/>
<field name="click_rate" string="Click %"/>
</tree>
</field>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_mailchimp_lists_tree" model="ir.ui.view">
<field name="name">mailchimp.lists.tree</field>
<field name="model">mailchimp.lists</field>
<field name="arch" type="xml">
<tree string="MailChimp Lists/Audiences">
<field name="name"/>
<field name="date_created"/>
<field name="list_id"/>
<field name="partner_id"/>
<field name="list_rating"/>
<!--<button name="export_in_mailchimp" attrs="{'invisible': [('list_id','!=', False)]}" string="Export In MailChimp" type="object" icon="fa-external-link"/>-->
<!--<button name="update_in_mailchimp" attrs="{'invisible': [('list_id','=', False)]}" string="Update In MailChimp" type="object" icon="fa-share-square-o"/>-->
<button name="refresh_list" attrs="{'invisible': [('list_id','=', False)]}" string="Refresh"
type="object" icon="fa-refresh"/>
</tree>
</field>
</record>
<record id="view_mailchimp_lists_search" model="ir.ui.view">
<field name="name">mailchimp.lists.search</field>
<field name="model">mailchimp.lists</field>
<field name="arch" type="xml">
<search string="MailChimp Lists/Audiences">
<field name="name"/>
<field name="list_id"/>
<field name="account_id"/>
<group expand="0" string="Group By...">
<filter string="Account" name="account" domain="[]" context="{'group_by':'account_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_mailchimp_lists" model="ir.actions.act_window">
<field name="name">MailChimp Lists/Audiences</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mailchimp.lists</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{}</field>
<field name="help" type="html">
<p>
Retrieve the Lists/Audiences of configured lists on the account using Import
opeations.
</p>
</field>
</record>
<menuitem name="Lists/Audiences" parent="menu_mailchimp_root"
action="action_mailchimp_lists" id="menu_action_mailchimp_listss"
sequence="20"/>
</data>
</odoo>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_mailchimp_templates_form" model="ir.ui.view">
<field name="name">mailchimp.templates.form</field>
<field name="model">mailchimp.templates</field>
<field name="arch" type="xml">
<form string="MailChimp Templates" create="0">
<header>
</header>
<sheet>
<div class="oe_title">
<label class="oe_edit_only" for="name" string="Template Name"/>
<h1>
<field name="name" placeholder="Template Name"/>
</h1>
<div name="options">
<div>
<field name="drag_and_drop"/>
<label for="drag_and_drop"/>
</div>
<div>
<field name="responsive"/>
<label for="responsive"/>
</div>
<div>
<label for="account_id" string="Associated Account"/>
<field name="account_id" required="1"/>
</div>
</div>
</div>
<group>
<group>
<field name="type"/>
<field name="category"/>
<field name="share_url"/>
</group>
<group>
<field name="date_created" readonly="1"/>
<field name="date_edited" readonly="1"/>
<field name="folder_id" readonly="1"/>
</group>
</group>
<notebook>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_mailchimp_templates_tree" model="ir.ui.view">
<field name="name">mailchimp.templates.tree</field>
<field name="model">mailchimp.templates</field>
<field name="arch" type="xml">
<tree string="MailChimp Template" create="0">
<field name="name"/>
<field name="date_created"/>
<field name="type"/>
<field name="drag_and_drop"/>
<field name="responsive"/>
<field name="category"/>
</tree>
</field>
</record>
<record id="action_mailchimp_templates" model="ir.actions.act_window">
<field name="name">MailChimp Templates</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mailchimp.templates</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context"></field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new MailChimp Template.
</p>
</field>
</record>
<menuitem name="Templates" parent="menu_mailchimp_root"
action="action_mailchimp_templates" id="menu_action_mailchimp_templates"
sequence="30"/>
</data>
</odoo>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_mail_mass_mailing_contact_mail_form" model="ir.ui.view">
<field name="name">mail.mass_mailing.contact.form</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_contact_form"/>
<field name="arch" type="xml">
<field name="title_id" position="before">
<field name="related_partner_id" />
</field>
</field>
</record>
<record id="view_mail_mass_mailing_contact_tree" model="ir.ui.view">
<field name="name">mail.mass_mailing.contact.tree</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_contact_tree"/>
<field name="arch" type="xml">
<field name="opt_out" position="after">
<field name="is_email_valid"/>
<field name="pending_for_export" invisible="1" />
</field>
</field>
</record>
<record id="view_mail_mass_mailing_contact_search" model="ir.ui.view">
<field name="name">mail.mass_mailing.contact.search</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_contact_search"/>
<field name="arch" type="xml">
<filter name="not_opt_out" position="after">
<separator/>
<filter string="Pending for Export" name="pending_export" domain="[('pending_for_export', '=', True)]"/>
<separator/>
<filter string="Unsubscribers" name="unsub_contact" domain="[('opt_out', '=', True), ('is_email_valid', '=', True)]"
invisible="'default_list_ids' not in context"/>
<filter string="Cleaned" name="cleaned_contact" domain="[('is_email_valid', '=', False)]"
invisible="'default_list_ids' not in context"/>
</filter>
</field>
</record>
<record id="ir_actions_mass_mailing_contact_export" model="ir.actions.server">
<field name="name">Export To MailChimp</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="mass_mailing.model_mail_mass_mailing_contact"/>
<field name="state">code</field>
<field name="code">
if records:
records.action_export_to_mailchimp()
</field>
<field name="binding_model_id" ref="mass_mailing.model_mail_mass_mailing_contact"/>
</record>
<record id="ir_actions_mass_mailing_contact_update" model="ir.actions.server">
<field name="name">Update To MailChimp</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="mass_mailing.model_mail_mass_mailing_contact"/>
<field name="state">code</field>
<field name="code">
if records:
records.action_update_to_mailchimp()
</field>
<field name="binding_model_id" ref="mass_mailing.model_mail_mass_mailing_contact"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_mail_mass_mailing_list_form" model="ir.ui.view">
<field name="name">mail.mass_mailing.list.form</field>
<field name="model">mail.mass_mailing.list</field>
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_list_form"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('oe_title')]/h1" position="after">
<div name="options" groups="base.group_user">
<div>
<label for="mailchimp_list_id" string="Associated MailChimp Account"/>
<field name="mailchimp_list_id"/>
</div>
</div>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_mail_mass_mailing_form" model="ir.ui.view">
<field name="name">mail.mass_mailing.form</field>
<field name="model">mail.mass_mailing</field>
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_form"/>
<field name="arch" type="xml">
<button name="put_in_queue" position="after">
<button name="action_schedule_date" type="object" attrs="{'invisible': [('state', 'in', ('in_queue', 'done'))]}" class="btn-secondary" string="Schedule"/>
</button>
<xpath expr="//sheet/group[1]" position="inside">
<label for="mailchimp_template_id" string="MailChimp Template"/>
<div>
<field name="mailchimp_template_id" nolabel="1"/>
</div>
<label for="mailchimp_segment_id"/>
<div>
<field name="mailchimp_list_id" invisible="1"/>
<field name="mailchimp_segment_id" nolabel="1" domain="[('list_id','=', mailchimp_list_id)]"/>
</div>
<label for="mailchimp_champ_type" string="Type" attrs="{'required':[('mailchimp_template_id','!=',False)], 'invisible' : [('mailchimp_template_id','=',False)]}"/>
<div>
<field name="mailchimp_champ_type" nolabel="1" attrs="{'required':[('mailchimp_template_id','!=',False)], 'invisible' : [('mailchimp_template_id','=',False)]}"/>
</div>
</xpath>
</field>
</record>
<record id="view_mail_mass_mailing_kanban" model="ir.ui.view">
<field name="name">mail.mass_mailing.kanban</field>
<field name="model">mail.mass_mailing</field>
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_kanban"/>
<field name="arch" type="xml">
<field name="color" position="after">
<field name="mailchimp_id"/>
</field>
<xpath expr="//div[hasclass('o_kanban_record_headings')]/h3" position="after">
<t t-if="record.mailchimp_id.raw_value">
<span class="badge badge-primary oe_inline o_enterprise_label">
MailChimp
</span>
</t>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_partner_mailchimp_form" model="ir.ui.view">
<field name="name">res.partner.mailchimp.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook[last()]" position="inside">
<page string="MailChimp List" name="mailchimp_list">
<field name="subscription_list_ids" domain="[('mailchimp_list_id','!=',False)]" readonly="True">
<tree>
<field name="list_id"/>
<field name="opt_out"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,4 @@
from . import import_export_operation_wizard
from . import test_mailing
from . import mass_mailing_schedule_date
from . import partner_export_update_wizard

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="import_export_operation_form" model="ir.ui.view">
<field name="name">Import/Export Operation</field>
<field name="model">mailchimp.import.export.operation</field>
<field name="arch" type="xml">
<form string="Import/Export">
<group>
<field name='account_ids' string="Account(s)" widget="many2many_tags" options="{'no_create':True,'no_create_edit': True}"/>
</group>
<group>
<group string="Import">
<field name="get_lists" />
<field name="get_templates" />
<field name="get_campaigns" />
</group>
</group>
<footer>
<button string="Process" class="oe_highlight" type="object" name="process_operation"/>
<button string="Cancel" class="oe_highlight" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_import_export_operation" model="ir.actions.act_window">
<field name="name">Import/Export Operations</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mailchimp.import.export.operation</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem name="MailChimp Operations" parent="menu_mailchimp_root"
action="action_import_export_operation" id="menu_action_mailchimp_opearation"
sequence="20"/>
</data>
</odoo>

View File

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class ImportExportOperation(models.TransientModel):
_name = "mailchimp.import.export.operation"
_description = "Import/Export Operation"
account_ids = fields.Many2many('mailchimp.accounts', required=True,
help="Select Account from which you want to perform import/export operation")
get_lists = fields.Boolean("Lists/Audiences", help="Obtains available lists from MailChimp")
get_templates = fields.Boolean("Templates", help="Get a list of an account's available templates.")
get_campaigns = fields.Boolean("Campaigns", help="Get a list of campaigns.")
@api.model
def default_get(self, fields):
res = super(ImportExportOperation, self).default_get(fields)
accounts = self.env['mailchimp.accounts'].search([])
res.update({'account_ids': [(6, 0, accounts.ids)]})
return res
@api.multi
def process_operation(self):
for account in self.account_ids:
if self.get_lists:
account.import_lists()
if self.get_templates:
account.import_templates()
if self.get_campaigns:
account.import_campaigns()
return True

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
class MassMailingScheduleDate(models.TransientModel):
_name = 'mass.mailing.schedule.date'
_description = 'Mass Mailing Scheduling'
schedule_date = fields.Datetime(string='Schedule in the Future')
mass_mailing_id = fields.Many2one('mail.mass_mailing', required=True)
@api.constrains('schedule_date')
def _check_schedule_date(self):
for scheduler in self:
if scheduler.schedule_date < fields.Datetime.now():
raise ValidationError(_('Please select a date equal/or greater than the current date.'))
def set_schedule_date(self):
self.ensure_one()
mailing = self.mass_mailing_id
if mailing.mailchimp_template_id:
schedule_date = datetime.strptime(self.schedule_date, DEFAULT_SERVER_DATETIME_FORMAT)
mailing.schedule_mailchimp_champaign(schedule_date)
self.mass_mailing_id.write({'schedule_date': self.schedule_date, 'state': 'in_queue'})

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="mass_mailing_schedule_date_view_form" model="ir.ui.view">
<field name="name">mass.mailing.schedule.date.view.form</field>
<field name="model">mass.mailing.schedule.date</field>
<field name="arch" type="xml">
<form string="Take Future Schedule Date">
<group>
<group>
<field name="schedule_date" required="1"/>
</group>
</group>
<p class="oe_grey">
If you are using MailChimp Campaigns then Campaigns may only be scheduled to send on the
quarter-hour (:00, :15, :30, :45)
</p>
<footer>
<button string="Schedule" name="set_schedule_date" type="object" class="btn-primary"/>
<button string="Discard " class="btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="mass_mailing_schedule_date_action" model="ir.actions.act_window">
<field name="name">When do you want to send your mailing?</field>
<field name="res_model">mass.mailing.schedule.date</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class ParterExportMailchimp(models.TransientModel):
_name = 'partner.export.mailchimp'
odoo_list_ids = fields.Many2many('mailchimp.lists', string='MailChimp Lists', domain=[('odoo_list_id', '!=', False)])
@api.multi
def get_mailing_contact_id(self, partner_id, force_create=False):
mailing_contact_obj = self.env['mail.mass_mailing.contact']
if not partner_id.email:
return False
query = """
SELECT id
FROM mail_mass_mailing_contact
WHERE LOWER(substring(email, '([^ ,;<@]+@[^> ,;]+)')) = LOWER(substring('{}', '([^ ,;<@]+@[^> ,;]+)'))""".format(
partner_id.email)
self._cr.execute(query)
contact_id = self._cr.fetchone()
contact_id = contact_id[0] if contact_id else False
prepared_vals = {'name': partner_id.name, 'email': partner_id.email, 'tag_ids': [(6, 0, partner_id.category_id.ids)], 'country_id': partner_id.country_id.id}
if contact_id:
contact_id = mailing_contact_obj.browse(contact_id)
contact_id.write(prepared_vals)
if not contact_id and force_create:
contact_id = mailing_contact_obj.create(prepared_vals)
return contact_id.id
@api.multi
def action_export_partner_mailchimp(self):
mailing_contact_obj = self.env['mail.mass_mailing.contact']
partner_ids = self.env['res.partner'].search([('id', 'in', self._context.get('active_ids', []))])
for partner_id in partner_ids:
for odoo_list_id in self.odoo_list_ids:
contact_id = self.get_mailing_contact_id(partner_id, force_create=True)
if contact_id:
contact_id = mailing_contact_obj.browse(contact_id)
if odoo_list_id.id not in contact_id.subscription_list_ids.mapped('list_id').mapped('mailchimp_list_id').ids:
vals = {'list_id': odoo_list_id.odoo_list_id.id, 'contact_id': contact_id.id}
contact_id.subscription_list_ids.create(vals)
contact_id.action_export_to_mailchimp()
return True
@api.multi
def action_update_partner_mailchimp(self):
mailing_contact_obj = self.env['mail.mass_mailing.contact']
partner_ids = self.env['res.partner'].search([('id', 'in', self._context.get('active_ids', []))])
for partner_id in partner_ids:
contact_id = self.get_mailing_contact_id(partner_id)
if contact_id:
contact_id = mailing_contact_obj.browse(contact_id)
contact_id.action_update_to_mailchimp()
return True

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="mailchimp_export_update_view_form" model="ir.ui.view">
<field name="name">partner.export.mailchimp.form</field>
<field name="model">partner.export.mailchimp</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group invisible="context.get('update_in_mailchimp', False)">
<field name="odoo_list_ids" required="not context.get('update_in_mailchimp', False)" widget="many2many_tags"/>
</group>
</sheet>
<footer>
<group>
<span>
<button string="Update" invisible="not context.get('update_in_mailchimp', False)" type="object" class="oe_highlight" name="action_update_partner_mailchimp"/>
<button string="Export" invisible="context.get('update_in_mailchimp', False)" type="object" class="oe_highlight" name="action_export_partner_mailchimp"/>
<button string="Cancel" class="oe_link" special="cancel" />
</span>
</group>
</footer>
</form>
</field>
</record>
<record id="mailchimp_export_update_form_action" model="ir.actions.act_window">
<field name="name">Export to MailChimp</field>
<field name="res_model">partner.export.mailchimp</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<!-- Add action entry in the Action Menu for Partners -->
<act_window id="mailchimp_export_action"
name="Export to MailChimp"
src_model="res.partner"
res_model="partner.export.mailchimp"
view_type="form"
view_mode="form"
key2="client_action_multi"
target="new"/>
<act_window id="mailchimp_update_action"
name="Update to MailChimp"
src_model="res.partner"
res_model="partner.export.mailchimp"
view_type="form"
view_mode="form"
key2="client_action_multi"
context="{'update_in_mailchimp': True}"
target="new"/>
</odoo>

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, tools
class TestMassMailing(models.TransientModel):
_inherit = 'mail.mass_mailing.test'
@api.multi
def send_mail_test(self):
self.ensure_one()
mails = self.env['mail.mail']
mailing = self.mass_mailing_id
test_emails = tools.email_split(self.email_to)
if mailing.mailchimp_template_id:
return mailing.send_test_mail_mailchimp(test_emails)
mass_mail_layout = self.env.ref('mass_mailing.mass_mailing_mail_layout')
for test_mail in test_emails:
# Convert links in absolute URLs before the application of the shortener
mailing.write({'body_html': self.env['mail.thread']._replace_local_links(mailing.body_html)})
body = tools.html_sanitize(mailing.body_html, sanitize_attributes=True, sanitize_style=True)
mail_values = {
'email_from': mailing.email_from,
'reply_to': mailing.reply_to,
'email_to': test_mail,
'subject': mailing.name,
'body_html': mass_mail_layout.render({'body': body}, engine='ir.qweb', minimal_qcontext=True),
'notification': True,
'mailing_id': mailing.id,
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
'auto_delete': True,
}
mail = self.env['mail.mail'].create(mail_values)
mails |= mail
mails.send()
return True