[MERGE] utm, link_tracker, mass_mailing: improve views usability and lint module
Purpose of this merge is to clean a bit link_tracker and utm as well as their
use in mass mailing. Main things done in this merge are
* reorganize link_tracker and utm according to guidelines. Purpose is
to ease code understanding and prepare future improvements in those
modules;
* clean views, add missing fields in views;
* better integration of link menus and views in mass mailing;
This commit is related to task ID 1896681.
closes odoo/odoo#27975
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controller
|
||||
from . import controller
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Link Tracker',
|
||||
'category': 'Marketing',
|
||||
'description': """
|
||||
Create short and trackable URLs.
|
||||
=====================================================
|
||||
|
||||
""",
|
||||
Shorten URLs and use them to track clicks and UTMs
|
||||
""",
|
||||
'version': '1.0',
|
||||
'depends': ['utm'],
|
||||
'data': [
|
||||
'views/link_tracker.xml',
|
||||
'views/link_tracker_views.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import link_tracker
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import random
|
||||
import re
|
||||
import requests
|
||||
import string
|
||||
|
||||
import requests
|
||||
from lxml import html
|
||||
from werkzeug import urls, utils
|
||||
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.tools import ustr
|
||||
|
||||
|
||||
URL_REGEX = r'(\bhref=[\'"](?!mailto:|tel:|sms:)([^\'"]+)[\'"])'
|
||||
|
||||
|
||||
def VALIDATE_URL(url):
|
||||
if urls.url_parse(url).scheme not in ('http', 'https', 'ftp', 'ftps'):
|
||||
return 'http://' + url
|
||||
@@ -23,50 +24,32 @@ def VALIDATE_URL(url):
|
||||
return url
|
||||
|
||||
|
||||
class link_tracker(models.Model):
|
||||
"""link_tracker allow users to wrap any URL into a short and trackable URL.
|
||||
link_tracker counts clicks on each tracked link.
|
||||
This module is also used by mass_mailing, where each link in mail_mail html_body are converted into
|
||||
a trackable link to get the click-through rate of each mass_mailing."""
|
||||
class LinkTracker(models.Model):
|
||||
""" Link trackers allow users to wrap any URL into a short URL that can be
|
||||
tracked by Odoo. Clicks are counter on each link. A tracker is linked to
|
||||
UTMs allowing to analyze marketing actions.
|
||||
|
||||
This model is also used in mass_mailing where each link in html body is
|
||||
automatically converted into a short link that is tracked and integrates
|
||||
UTMs. """
|
||||
_name = "link.tracker"
|
||||
_rec_name = "short_url"
|
||||
_description = 'Link Tracker'
|
||||
|
||||
_inherit = ['utm.mixin']
|
||||
_description = "Link Tracker"
|
||||
_inherit = ["utm.mixin"]
|
||||
|
||||
# URL info
|
||||
url = fields.Char(string='Target URL', required=True)
|
||||
count = fields.Integer(string='Number of Clicks', compute='_compute_count', store=True)
|
||||
short_url = fields.Char(string='Tracked URL', compute='_compute_short_url')
|
||||
link_click_ids = fields.One2many('link.tracker.click', 'link_id', string='Clicks')
|
||||
title = fields.Char(string='Page Title', store=True)
|
||||
favicon = fields.Char(string='Favicon', compute='_compute_favicon', store=True)
|
||||
link_code_ids = fields.One2many('link.tracker.code', 'link_id', string='Codes')
|
||||
code = fields.Char(string='Short URL code', compute='_compute_code')
|
||||
redirected_url = fields.Char(string='Redirected URL', compute='_compute_redirected_url')
|
||||
short_url_host = fields.Char(string='Host of the short URL', compute='_compute_short_url_host')
|
||||
title = fields.Char(string='Page Title', store=True)
|
||||
favicon = fields.Char(string='Favicon', compute='_compute_favicon', store=True)
|
||||
icon_src = fields.Char(string='Favicon Source', compute='_compute_icon_src')
|
||||
|
||||
@api.model
|
||||
def convert_links(self, html, vals, blacklist=None):
|
||||
for match in re.findall(URL_REGEX, html):
|
||||
|
||||
short_schema = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/r/'
|
||||
|
||||
href = match[0]
|
||||
long_url = match[1]
|
||||
|
||||
vals['url'] = utils.unescape(long_url)
|
||||
|
||||
if not blacklist or not [s for s in blacklist if s in long_url] and not long_url.startswith(short_schema):
|
||||
link = self.create(vals)
|
||||
shorten_url = self.browse(link.id)[0].short_url
|
||||
|
||||
if shorten_url:
|
||||
new_href = href.replace(long_url, shorten_url)
|
||||
html = html.replace(href, new_href)
|
||||
|
||||
return html
|
||||
# Tracking
|
||||
link_code_ids = fields.One2many('link.tracker.code', 'link_id', string='Codes')
|
||||
code = fields.Char(string='Short URL code', compute='_compute_code')
|
||||
link_click_ids = fields.One2many('link.tracker.click', 'link_id', string='Clicks')
|
||||
count = fields.Integer(string='Number of Clicks', compute='_compute_count', store=True)
|
||||
|
||||
@api.one
|
||||
@api.depends('link_click_ids.link_id')
|
||||
@@ -130,32 +113,6 @@ class link_tracker(models.Model):
|
||||
|
||||
self.favicon = icon_base64
|
||||
|
||||
@api.multi
|
||||
def action_view_statistics(self):
|
||||
action = self.env['ir.actions.act_window'].for_xml_id('link_tracker', 'action_view_click_statistics')
|
||||
action['domain'] = [('link_id', '=', self.id)]
|
||||
return action
|
||||
|
||||
@api.multi
|
||||
def action_visit_page(self):
|
||||
return {
|
||||
'name': _("Visit Webpage"),
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.url,
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
@api.model
|
||||
def recent_links(self, filter, limit):
|
||||
if filter == 'newest':
|
||||
return self.search_read([], order='create_date DESC', limit=limit)
|
||||
elif filter == 'most-clicked':
|
||||
return self.search_read([('count', '!=', 0)], order='count DESC', limit=limit)
|
||||
elif filter == 'recently-used':
|
||||
return self.search_read([('count', '!=', 0)], order='write_date DESC', limit=limit)
|
||||
else:
|
||||
return {'Error': "This filter doesn't exist."}
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
create_vals = vals.copy()
|
||||
@@ -182,13 +139,60 @@ class link_tracker(models.Model):
|
||||
if fname not in create_vals:
|
||||
create_vals[fname] = False
|
||||
|
||||
link = super(link_tracker, self).create(create_vals)
|
||||
link = super(LinkTracker, self).create(create_vals)
|
||||
|
||||
code = self.env['link.tracker.code'].get_random_code_string()
|
||||
self.env['link.tracker.code'].sudo().create({'code': code, 'link_id': link.id})
|
||||
|
||||
return link
|
||||
|
||||
@api.model
|
||||
def convert_links(self, html, vals, blacklist=None):
|
||||
for match in re.findall(URL_REGEX, html):
|
||||
|
||||
short_schema = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/r/'
|
||||
|
||||
href = match[0]
|
||||
long_url = match[1]
|
||||
|
||||
vals['url'] = utils.unescape(long_url)
|
||||
|
||||
if not blacklist or not [s for s in blacklist if s in long_url] and not long_url.startswith(short_schema):
|
||||
link = self.create(vals)
|
||||
shorten_url = self.browse(link.id)[0].short_url
|
||||
|
||||
if shorten_url:
|
||||
new_href = href.replace(long_url, shorten_url)
|
||||
html = html.replace(href, new_href)
|
||||
|
||||
return html
|
||||
|
||||
@api.multi
|
||||
def action_view_statistics(self):
|
||||
action = self.env['ir.actions.act_window'].for_xml_id('link_tracker', 'link_tracker_click_action_statistics')
|
||||
action['domain'] = [('link_id', '=', self.id)]
|
||||
return action
|
||||
|
||||
@api.multi
|
||||
def action_visit_page(self):
|
||||
return {
|
||||
'name': _("Visit Webpage"),
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.url,
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
@api.model
|
||||
def recent_links(self, filter, limit):
|
||||
if filter == 'newest':
|
||||
return self.search_read([], order='create_date DESC', limit=limit)
|
||||
elif filter == 'most-clicked':
|
||||
return self.search_read([('count', '!=', 0)], order='count DESC', limit=limit)
|
||||
elif filter == 'recently-used':
|
||||
return self.search_read([('count', '!=', 0)], order='write_date DESC', limit=limit)
|
||||
else:
|
||||
return {'Error': "This filter doesn't exist."}
|
||||
|
||||
@api.model
|
||||
def get_url_from_code(self, code, context=None):
|
||||
code_rec = self.env['link.tracker.code'].sudo().search([('code', '=', code)])
|
||||
@@ -203,13 +207,17 @@ class link_tracker(models.Model):
|
||||
]
|
||||
|
||||
|
||||
class link_tracker_code(models.Model):
|
||||
class LinkTrackerCode(models.Model):
|
||||
_name = "link.tracker.code"
|
||||
_description = 'Link Tracker Code'
|
||||
_description = "Link Tracker Code"
|
||||
|
||||
code = fields.Char(string='Short URL Code', store=True)
|
||||
link_id = fields.Many2one('link.tracker', 'Link', required=True, ondelete='cascade')
|
||||
|
||||
_sql_constraints = [
|
||||
('code', 'unique( code )', 'Code must be unique.')
|
||||
]
|
||||
|
||||
@api.model
|
||||
def get_random_code_string(self):
|
||||
size = 3
|
||||
@@ -221,18 +229,14 @@ class link_tracker_code(models.Model):
|
||||
else:
|
||||
return code_proposition
|
||||
|
||||
_sql_constraints = [
|
||||
('code', 'unique( code )', 'Code must be unique.')
|
||||
]
|
||||
|
||||
|
||||
class link_tracker_click(models.Model):
|
||||
class LinkTrackerClick(models.Model):
|
||||
_name = "link.tracker.click"
|
||||
_rec_name = "link_id"
|
||||
_description = 'Link Tracker Click'
|
||||
_description = "Link Tracker Click"
|
||||
|
||||
click_date = fields.Date(string='Create Date')
|
||||
link_id = fields.Many2one('link.tracker', 'Link', required=True, ondelete='cascade')
|
||||
click_date = fields.Date(string='Create Date')
|
||||
ip = fields.Char(string='Internet Protocol')
|
||||
country_id = fields.Many2one('res.country', 'Country')
|
||||
|
||||
|
||||
+47
-75
@@ -1,28 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record model="ir.actions.act_window" id="action_view_click_statistics">
|
||||
<field name="name">Statistics of Clicks</field>
|
||||
<field name="res_model">link.tracker.click</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="domain">[('link_id', 'IN', ids)]</field>
|
||||
</record>
|
||||
|
||||
<!-- Search -->
|
||||
<record id="view_link_tracker_filter" model="ir.ui.view">
|
||||
<field name="name">Links</field>
|
||||
<!-- LINT.TRACKER -->
|
||||
<record id="link_tracker_view_search" model="ir.ui.view">
|
||||
<field name="name">link.tracker.view.search</field>
|
||||
<field name="model">link.tracker</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Tracked Link">
|
||||
<field name="title"/>
|
||||
<search string="Links">
|
||||
<field name="url" string="Title and URL" filter_domain="['|', ('title', 'ilike', self), ('url', 'ilike', self)]"/>
|
||||
<field name="title"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Website links views -->
|
||||
<record id="view_link_tracker_form" model="ir.ui.view">
|
||||
<field name="name">link.tracker.form</field>
|
||||
<record id="link_tracker_view_form" model="ir.ui.view">
|
||||
<field name="name">link.tracker.view.form</field>
|
||||
<field name="model">link.tracker</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Website Link">
|
||||
@@ -34,56 +26,29 @@
|
||||
<button type="object" class="oe_stat_button" name="action_view_statistics" icon="fa-bar-chart-o">
|
||||
<field name="count" string="Clicks" widget="statinfo"/>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<group>
|
||||
<field name="title"/>
|
||||
<field name="url"/>
|
||||
<field name="short_url"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="campaign_id"/>
|
||||
<field name="medium_id"/>
|
||||
<field name="source_id"/>
|
||||
<group name="url" string="URL">
|
||||
<field name="title"/>
|
||||
<field name="url"/>
|
||||
<field name="short_url"/>
|
||||
</group>
|
||||
<group name="utm" string="UTM">
|
||||
<field name="campaign_id"/>
|
||||
<field name="medium_id"/>
|
||||
<field name="source_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_link_tracker_form_stats" model="ir.ui.view">
|
||||
<field name="name">link.tracker.my.form</field>
|
||||
<record id="link_tracker_view_tree" model="ir.ui.view">
|
||||
<field name="name">link.tracker.view.tree</field>
|
||||
<field name="model">link.tracker</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Website Link Graph">
|
||||
<sheet>
|
||||
<field name="link_click_ids">
|
||||
<graph>
|
||||
<field name="link_id"/>
|
||||
<field name="ip"/>
|
||||
<field name="country_id"/>
|
||||
</graph>
|
||||
</field>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_link_tracker_stats">
|
||||
<field name="name">link.tracker.form.graph.action</field>
|
||||
<field name="res_model">link.tracker</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_link_tracker_form_stats"/>
|
||||
</record>
|
||||
|
||||
<record id="view_link_tracker_tree" model="ir.ui.view">
|
||||
<field name="name">link.tracker.tree</field>
|
||||
<field name="model">link.tracker</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Website Links">
|
||||
<tree string="Links">
|
||||
<field name="create_date"/>
|
||||
<field name="title"/>
|
||||
<field name="url"/>
|
||||
@@ -93,24 +58,23 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_link_tracker_graph" model="ir.ui.view" >
|
||||
<field name="name">link.tracker.graph</field>
|
||||
<record id="link_tracker_view_graph" model="ir.ui.view">
|
||||
<field name="name">link.tracker.view.graph</field>
|
||||
<field name="model">link.tracker</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Website Links" type="bar">
|
||||
<graph string="Links" type="bar">
|
||||
<field name="url" type="row"/>
|
||||
<field name="count" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action for website links-->
|
||||
<record model="ir.actions.act_window" id="action_link_tracker">
|
||||
<record id="link_tracker_action" model="ir.actions.act_window">
|
||||
<field name="name">Link Tracker</field>
|
||||
<field name="res_model">link.tracker</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="view_id" ref="view_link_tracker_tree"/>
|
||||
<field name="view_id" ref="link_tracker_view_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new link tracker
|
||||
@@ -118,14 +82,12 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Website links click views -->
|
||||
<record id="view_link_tracker_click_form" model="ir.ui.view">
|
||||
<field name="name">link.tracker.click.form</field>
|
||||
<!-- LINK.TRACKER.CLICK -->
|
||||
<record id="link_tracker_click_view_form" model="ir.ui.view">
|
||||
<field name="name">link.tracker.click.view.form</field>
|
||||
<field name="model">link.tracker.click</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Website Link Clicks">
|
||||
<header>
|
||||
</header>
|
||||
<form string="Link Click">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="link_id"/>
|
||||
@@ -137,11 +99,11 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_link_tracker_click_tree" model="ir.ui.view">
|
||||
<field name="name">link.tracker.click.tree</field>
|
||||
<record id="link_tracker_click_view_tree" model="ir.ui.view">
|
||||
<field name="name">link.tracker.click.view.tree</field>
|
||||
<field name="model">link.tracker.click</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Website Links Clicks">
|
||||
<tree string="Links Clicks">
|
||||
<field name="link_id"/>
|
||||
<field name="ip"/>
|
||||
<field name="country_id"/>
|
||||
@@ -149,11 +111,11 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_link_tracker_click_graph" model="ir.ui.view" >
|
||||
<field name="name">link.tracker.click.graph</field>
|
||||
<record id="link_tracker_click_view_graph" model="ir.ui.view">
|
||||
<field name="name">link.tracker.click.view.graph</field>
|
||||
<field name="model">link.tracker.click</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Website Link Clicks">
|
||||
<graph string="Link Clicks">
|
||||
<field name="link_id"/>
|
||||
<field name="ip"/>
|
||||
<field name="country_id"/>
|
||||
@@ -161,8 +123,18 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action for website links click-->
|
||||
<record id="link_tracker_click_action_statistics" model="ir.actions.act_window">
|
||||
<field name="name">Click Statistics</field>
|
||||
<field name="res_model">link.tracker.click</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="domain">[]</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_url_shortener_main" name="Link Tracker" parent="utm.menu_link_tracker_root" action="action_link_tracker" groups="base.group_no_one"/>
|
||||
<!-- MENUS -->
|
||||
<menuitem id="link_tracker_menu_main"
|
||||
name="Link Tracker"
|
||||
parent="utm.menu_link_tracker_root"
|
||||
action="link_tracker_action"
|
||||
groups="base.group_no_one"/>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -25,6 +25,7 @@
|
||||
'wizard/mass_mailing_list_merge_views.xml',
|
||||
'wizard/test_mailing_views.xml',
|
||||
'wizard/mass_mailing_schedule_date_views.xml',
|
||||
'views/mass_mailing_views_menus.xml',
|
||||
'views/mass_mailing_report_views.xml',
|
||||
'views/mass_mailing_stats_views.xml',
|
||||
'views/link_tracker_views.xml',
|
||||
|
||||
@@ -1,42 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- LINK TRACKER VIEWS -->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_links_tree">
|
||||
<field name="name">link.tracker.tree</field>
|
||||
<!-- LINK.TRACKER VIEWS -->
|
||||
<record id="link_tracker_view_search" model="ir.ui.view">
|
||||
<field name="name">link.tracker.view.search.inherit.mass.mail</field>
|
||||
<field name="model">link.tracker</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="inherit_id" ref="link_tracker.link_tracker_view_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mailing Statistics of Clicks">
|
||||
<field name="title"/>
|
||||
<field name="url"/>
|
||||
<field name="count"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_links_search">
|
||||
<field name="name">link.tracker.search</field>
|
||||
<field name="model">link.tracker</field>
|
||||
<field name="inherit_id" ref="link_tracker.view_link_tracker_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<xpath expr="//field[@name='title']" position="after">
|
||||
<field name="mass_mailing_id"/>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
</search>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_view_mass_mailing_links_statistics">
|
||||
<record id="link_tracker_view_form" model="ir.ui.view">
|
||||
<field name="name">link.tracker.view.form.inherit.mass.mail</field>
|
||||
<field name="model">link.tracker</field>
|
||||
<field name="inherit_id" ref="link_tracker.link_tracker_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='campaign_id']" position="before">
|
||||
<field name="mass_mailing_id"/>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="link_tracker_action_mass_mailing" model="ir.actions.act_window">
|
||||
<field name="name">Statistics of Clicks</field>
|
||||
<field name="res_model">link.tracker</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="view_id" ref="link_tracker.link_tracker_view_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new link tracker
|
||||
</p>
|
||||
</field>
|
||||
<field name="context">{'search_default_mass_mailing_id': active_id}</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="dropdb snipp">
|
||||
|
||||
<record id="link_tracker_action_mass_mailing_campaign" model="ir.actions.act_window">
|
||||
<field name="name">Statistics of Clicks</field>
|
||||
<field name="res_model">link.tracker</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="context">{'search_default_mass_mailing_campaign_id': active_id}</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="view_id" ref="link_tracker.link_tracker_view_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new link tracker
|
||||
</p>
|
||||
</field>
|
||||
<field name="context">{'id="link_tracker_action_mass_mailing"': active_id}</field>
|
||||
</record>
|
||||
|
||||
<!-- MENU TO HANLDE LINK DATA IN MM -->
|
||||
<menuitem id="link_tracker_menu_mass_mailing"
|
||||
name="Link Tracker"
|
||||
parent="mass_mailing_configuration"
|
||||
sequence="5"
|
||||
action="link_tracker.link_tracker_action"/>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<menuitem id="mail_blacklist_mm_menu" name="Blacklist" action="mail.mail_blacklist_action" parent="menu_mass_mailing_configuration"/>
|
||||
<menuitem id="mail_blacklist_mm_menu" name="Blacklist" action="mail.mail_blacklist_action" parent="mass_mailing_configuration"/>
|
||||
</odoo>
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Marketing / Mailing -->
|
||||
<menuitem name="Email Marketing" id="mass_mailing_menu_root" sequence="60" web_icon="mass_mailing,static/description/icon.png"/>
|
||||
<menuitem name="Mailing Lists" id="mass_mailing_mailing_list_menu"
|
||||
parent="mass_mailing_menu_root" sequence="2" groups="mass_mailing.group_mass_mailing_user"/>
|
||||
<!-- Marketing / Configuration -->
|
||||
<menuitem name="Configuration" id="mass_mailing_configuration"
|
||||
parent="mass_mailing_menu_root" sequence="100"/>
|
||||
|
||||
<!-- MAILING CONTACT -->
|
||||
<record model="ir.ui.view" id="mail_mass_mailing_list_contact_rel_view_form">
|
||||
<field name="name">mail.mass_mailing.list_contact_rel.view.form</field>
|
||||
@@ -712,7 +703,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<a name="%(action_view_mass_mailing_links_statistics)d"
|
||||
<a name="%(link_tracker_action_mass_mailing)d"
|
||||
type="action">Clicks</a>
|
||||
</div>
|
||||
<div class="col-4 text-right">
|
||||
@@ -866,11 +857,8 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_mass_mailing_configuration" name="Configuration" parent="mass_mailing_menu_root"
|
||||
sequence="100" groups="mass_mailing.group_mass_mailing_user"/>
|
||||
|
||||
<menuitem name="Campaign Stages" id="menu_view_mass_mailing_stages"
|
||||
parent="menu_mass_mailing_configuration" sequence="1"
|
||||
parent="mass_mailing_configuration" sequence="1"
|
||||
groups="mass_mailing.group_mass_mailing_campaign"
|
||||
action="action_view_mass_mailing_stages"/>
|
||||
|
||||
@@ -897,7 +885,7 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="mass_mailing_tag_menu" parent="menu_mass_mailing_configuration" action="mass_mailing_tag_action" sequence="2" groups="base.group_no_one"/>
|
||||
<menuitem id="mass_mailing_tag_menu" parent="mass_mailing_configuration" action="mass_mailing_tag_action" sequence="2" groups="base.group_no_one"/>
|
||||
|
||||
|
||||
<!-- MAILING CAMPAIGNS !-->
|
||||
@@ -968,7 +956,7 @@
|
||||
type="action" class="oe_stat_button">
|
||||
<field name="replied_ratio" widget="percentpie" string="Replied"/>
|
||||
</button>
|
||||
<button name="%(dropdb snipp)d"
|
||||
<button name="%(link_tracker_action_mass_mailing_campaign)d"
|
||||
type="action" class="oe_stat_button">
|
||||
<field name="clicks_ratio" widget="percentpie" string="Clicks"/>
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Marketing / Mailing -->
|
||||
<menuitem name="Email Marketing" id="mass_mailing_menu_root" sequence="60" web_icon="mass_mailing,static/description/icon.png"/>
|
||||
<menuitem name="Mailing Lists" id="mass_mailing_mailing_list_menu"
|
||||
parent="mass_mailing_menu_root" sequence="2" groups="mass_mailing.group_mass_mailing_user"/>
|
||||
<!-- Marketing / Configuration -->
|
||||
<menuitem name="Configuration" id="mass_mailing_configuration"
|
||||
parent="mass_mailing_menu_root"
|
||||
sequence="100"
|
||||
groups="mass_mailing.group_mass_mailing_user"/>
|
||||
</odoo>
|
||||
@@ -74,8 +74,6 @@
|
||||
<field name="context">{'module' : 'mass_mailing'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_mass_mailing_configuration" name="Configuration" parent="mass_mailing_menu_root"
|
||||
sequence="100"/>
|
||||
<menuitem id="menu_mass_mailing_global_settings" name="Settings"
|
||||
parent="menu_mass_mailing_configuration" sequence="0" action="action_mass_mailing_configuration" groups="base.group_system"/>
|
||||
parent="mass_mailing_configuration" sequence="0" action="action_mass_mailing_configuration" groups="base.group_system"/>
|
||||
</odoo>
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
<separator/>
|
||||
<filter string="Starred" name="starred" domain="[('priority','in',[1, 2])]"/>
|
||||
<separator/>
|
||||
<filter string="Opened" name="opened" domain="[('stage_id.fold','=',False)]"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<separator/>
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'UTM Trackers',
|
||||
'category': 'Hidden',
|
||||
'description': """
|
||||
Enable UTM trackers in shared links.
|
||||
=====================================================
|
||||
""",
|
||||
Enable management of UTM trackers: campaign, medium, source.
|
||||
""",
|
||||
'version': '1.0',
|
||||
'depends': ['base'],
|
||||
'data': [
|
||||
'data/utm_data.xml',
|
||||
'views/utm_views.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/utm.xml',
|
||||
'data/utm_data.xml'
|
||||
],
|
||||
'demo': [],
|
||||
'auto_install': False,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import utm
|
||||
from . import utm_mixin
|
||||
from . import ir_http
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID
|
||||
from odoo.http import request
|
||||
from odoo.tools import pycompat
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class UtmMedium(models.Model):
|
||||
@@ -29,57 +27,3 @@ class UtmSource(models.Model):
|
||||
_description = 'UTM Source'
|
||||
|
||||
name = fields.Char(string='Source Name', required=True, translate=True)
|
||||
|
||||
|
||||
class UtmMixin(models.AbstractModel):
|
||||
|
||||
"""Mixin class for objects which can be tracked by marketing. """
|
||||
_name = 'utm.mixin'
|
||||
_description = 'UTM Mixin'
|
||||
|
||||
campaign_id = fields.Many2one('utm.campaign', 'Campaign',
|
||||
help="This is a name that helps you keep track of your different campaign efforts, e.g. Fall_Drive, Christmas_Special")
|
||||
source_id = fields.Many2one('utm.source', 'Source',
|
||||
help="This is the source of the link, e.g. Search Engine, another domain, or name of email list")
|
||||
medium_id = fields.Many2one('utm.medium', 'Medium',
|
||||
help="This is the method of delivery, e.g. Postcard, Email, or Banner Ad", oldname='channel_id')
|
||||
|
||||
def tracking_fields(self):
|
||||
# This function cannot be overridden in a model which inherit utm.mixin
|
||||
# Limitation by the heritage on AbstractModel
|
||||
# record_crm_lead.tracking_fields() will call tracking_fields() from module utm.mixin (if not overridden on crm.lead)
|
||||
# instead of the overridden method from utm.mixin.
|
||||
# To force the call of overridden method, we use self.env['utm.mixin'].tracking_fields() which respects overridden
|
||||
# methods of utm.mixin, but will ignore overridden method on crm.lead
|
||||
return [
|
||||
# ("URL_PARAMETER", "FIELD_NAME_MIXIN", "NAME_IN_COOKIES")
|
||||
('utm_campaign', 'campaign_id', 'odoo_utm_campaign'),
|
||||
('utm_source', 'source_id', 'odoo_utm_source'),
|
||||
('utm_medium', 'medium_id', 'odoo_utm_medium'),
|
||||
]
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
values = super(UtmMixin, self).default_get(fields)
|
||||
|
||||
# We ignore UTM for salemen, except some requests that could be done as superuser_id to bypass access rights.
|
||||
if self.env.uid != SUPERUSER_ID and self.env.user.has_group('sales_team.group_sale_salesman'):
|
||||
return values
|
||||
|
||||
for url_param, field_name, cookie_name in self.env['utm.mixin'].tracking_fields():
|
||||
if field_name in fields:
|
||||
field = self._fields[field_name]
|
||||
value = False
|
||||
if request:
|
||||
# ir_http dispatch saves the url params in a cookie
|
||||
value = request.httprequest.cookies.get(cookie_name)
|
||||
# if we receive a string for a many2one, we search/create the id
|
||||
if field.type == 'many2one' and isinstance(value, pycompat.string_types) and value:
|
||||
Model = self.env[field.comodel_name]
|
||||
records = Model.search([('name', '=', value)], limit=1)
|
||||
if not records:
|
||||
records = Model.create({'name': value})
|
||||
value = records.id
|
||||
if value:
|
||||
values[field_name] = value
|
||||
return values
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID
|
||||
from odoo.http import request
|
||||
from odoo.tools import pycompat
|
||||
|
||||
|
||||
class UtmMixin(models.AbstractModel):
|
||||
""" Mixin class for objects which can be tracked by marketing. """
|
||||
_name = 'utm.mixin'
|
||||
_description = 'UTM Mixin'
|
||||
|
||||
campaign_id = fields.Many2one('utm.campaign', 'Campaign',
|
||||
help="This is a name that helps you keep track of your different campaign efforts, e.g. Fall_Drive, Christmas_Special")
|
||||
source_id = fields.Many2one('utm.source', 'Source',
|
||||
help="This is the source of the link, e.g. Search Engine, another domain, or name of email list")
|
||||
medium_id = fields.Many2one('utm.medium', 'Medium',
|
||||
help="This is the method of delivery, e.g. Postcard, Email, or Banner Ad", oldname='channel_id')
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
values = super(UtmMixin, self).default_get(fields)
|
||||
|
||||
# We ignore UTM for salemen, except some requests that could be done as superuser_id to bypass access rights.
|
||||
if self.env.uid != SUPERUSER_ID and self.env.user.has_group('sales_team.group_sale_salesman'):
|
||||
return values
|
||||
|
||||
for url_param, field_name, cookie_name in self.env['utm.mixin'].tracking_fields():
|
||||
if field_name in fields:
|
||||
field = self._fields[field_name]
|
||||
value = False
|
||||
if request:
|
||||
# ir_http dispatch saves the url params in a cookie
|
||||
value = request.httprequest.cookies.get(cookie_name)
|
||||
# if we receive a string for a many2one, we search/create the id
|
||||
if field.type == 'many2one' and isinstance(value, pycompat.string_types) and value:
|
||||
Model = self.env[field.comodel_name]
|
||||
records = Model.search([('name', '=', value)], limit=1)
|
||||
if not records:
|
||||
records = Model.create({'name': value})
|
||||
value = records.id
|
||||
if value:
|
||||
values[field_name] = value
|
||||
return values
|
||||
|
||||
def tracking_fields(self):
|
||||
# This function cannot be overridden in a model which inherit utm.mixin
|
||||
# Limitation by the heritage on AbstractModel
|
||||
# record_crm_lead.tracking_fields() will call tracking_fields() from module utm.mixin (if not overridden on crm.lead)
|
||||
# instead of the overridden method from utm.mixin.
|
||||
# To force the call of overridden method, we use self.env['utm.mixin'].tracking_fields() which respects overridden
|
||||
# methods of utm.mixin, but will ignore overridden method on crm.lead
|
||||
return [
|
||||
# ("URL_PARAMETER", "FIELD_NAME_MIXIN", "NAME_IN_COOKIES")
|
||||
('utm_campaign', 'campaign_id', 'odoo_utm_campaign'),
|
||||
('utm_source', 'source_id', 'odoo_utm_source'),
|
||||
('utm_medium', 'medium_id', 'odoo_utm_medium'),
|
||||
]
|
||||
@@ -1,110 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem id="menu_link_tracker_root" name="Link Tracker" groups="base.group_no_one" sequence="30" web_icon="utm,static/description/icon.png"/>
|
||||
<menuitem name="UTMs" id="marketing_utm" parent="menu_link_tracker_root" sequence="99" groups="base.group_no_one"/>
|
||||
|
||||
<!-- utm.campaign -->
|
||||
<record model="ir.ui.view" id="utm_campaign_tree">
|
||||
<field name="name">utm.campaign.tree</field>
|
||||
<field name="model">utm.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Campaign">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="utm_campaign_form">
|
||||
<field name="name">utm.campaign.form</field>
|
||||
<field name="model">utm.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Campaign">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="utm_campaign_act" model="ir.actions.act_window">
|
||||
<field name="name">Campaigns</field>
|
||||
<field name="res_model">utm.campaign</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="utm_campaign_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem action="utm_campaign_act" id="menu_utm_campaign_act" sequence="4"
|
||||
groups="base.group_no_one" parent="marketing_utm"/>
|
||||
|
||||
<!-- utm.medium -->
|
||||
<record id="utm_medium_view_tree" model="ir.ui.view">
|
||||
<field name="name">utm.medium.tree</field>
|
||||
<field name="model">utm.medium</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Medium">
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="utm_medium_view_form" model="ir.ui.view">
|
||||
<field name="name">utm.medium.form</field>
|
||||
<field name="model">utm.medium</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Channel">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="utm_medium_action" model="ir.actions.act_window">
|
||||
<field name="name">Medium</field>
|
||||
<field name="res_model">utm.medium</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new medium
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="utm_medium_action" id="menu_utm_medium" parent="marketing_utm" sequence="4" groups="base.group_no_one"/>
|
||||
|
||||
<!-- utm.source -->
|
||||
<record id="utm_source_view_tree" model="ir.ui.view">
|
||||
<field name="name">utm.source.tree</field>
|
||||
<field name="model">utm.source</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Medium">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="utm_source_view_form" model="ir.ui.view">
|
||||
<field name="name">utm.source.form</field>
|
||||
<field name="model">utm.source</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Channel">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="utm_source_action" model="ir.actions.act_window">
|
||||
<field name="name">Sources</field>
|
||||
<field name="res_model">utm.source</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new source
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="utm_source_action" id="menu_utm_source" parent="marketing_utm" sequence="4" groups="base.group_no_one"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem id="menu_link_tracker_root"
|
||||
name="Link Tracker"
|
||||
sequence="30"
|
||||
web_icon="utm,static/description/icon.png"
|
||||
groups="base.group_no_one"/>
|
||||
<menuitem id="marketing_utm"
|
||||
name="UTMs"
|
||||
parent="menu_link_tracker_root"
|
||||
sequence="99"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
<!-- utm.campaign -->
|
||||
<record id="utm_campaign_view_tree" model="ir.ui.view">
|
||||
<field name="name">utm.campaign.view.tree</field>
|
||||
<field name="model">utm.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Campaign">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="utm_campaign_view_form" model="ir.ui.view">
|
||||
<field name="name">utm.campaign.view.form</field>
|
||||
<field name="model">utm.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Campaign">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="utm_campaign_action" model="ir.actions.act_window">
|
||||
<field name="name">Campaigns</field>
|
||||
<field name="res_model">utm.campaign</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="utm_campaign_view_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new UTM campaign
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_utm_campaign_act"
|
||||
action="utm_campaign_action"
|
||||
parent="marketing_utm"
|
||||
sequence="1"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
<!-- utm.medium -->
|
||||
<record id="utm_medium_view_tree" model="ir.ui.view">
|
||||
<field name="name">utm.medium.view.tree</field>
|
||||
<field name="model">utm.medium</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mediums">
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="utm_medium_view_form" model="ir.ui.view">
|
||||
<field name="name">utm.medium.view.form</field>
|
||||
<field name="model">utm.medium</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Medium">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="utm_medium_action" model="ir.actions.act_window">
|
||||
<field name="name">Mediums</field>
|
||||
<field name="res_model">utm.medium</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new UTM medium
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_utm_medium"
|
||||
action="utm_medium_action"
|
||||
parent="marketing_utm"
|
||||
sequence="5"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
<!-- utm.source -->
|
||||
<record id="utm_source_view_tree" model="ir.ui.view">
|
||||
<field name="name">utm.source.view.tree</field>
|
||||
<field name="model">utm.source</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Source">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="utm_source_view_form" model="ir.ui.view">
|
||||
<field name="name">utm.source.view.form</field>
|
||||
<field name="model">utm.source</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Source">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="utm_source_action" model="ir.actions.act_window">
|
||||
<field name="name">Sources</field>
|
||||
<field name="res_model">utm.source</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new UTM source
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_utm_source"
|
||||
action="utm_source_action"
|
||||
parent="marketing_utm"
|
||||
sequence="10"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user