[MOV] link_tracker, various: more link shortening tools to mail.render.mixin

PURPOSE

Move links shortening tools on mail.render.mixin to have rendering and
post procesing tools available on that mixin.

SPECIFICATIONS

Move html and text links shortening methods on mail.render.mixin class.
That way rendering and post processing is done in that mixin instead of
being specific to link.tracker model. Indeed those methods are tools and
not really business related methods.

Future commits will improve the way mail and sms composer use the render
mixin. This commit is therefore both a cleaning and preparing commit.

LINKS

Task ID 1963529
Community PR odoo/odoo#32397
This commit is contained in:
Thibault Delavallée
2020-03-24 10:25:04 +00:00
parent 3d86a29aeb
commit aaefa35e29
12 changed files with 94 additions and 71 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
Shorten URLs and use them to track clicks and UTMs
""",
'version': '1.0',
'depends': ['utm'],
'depends': ['utm', 'mail'],
'data': [
'views/link_tracker_views.xml',
'views/utm_campaign_views.xml',
+1
View File
@@ -2,4 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import link_tracker
from . import mail_render_mixin
from . import utm
+5 -53
View File
@@ -1,28 +1,14 @@
# -*- 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
from lxml import html
from werkzeug import urls, utils
from werkzeug import urls
from odoo import models, fields, api, _
URL_REGEX = r'(\bhref=[\'"](?!mailto:|tel:|sms:)([^\'"]+)[\'"])'
TEXT_URL_REGEX = r'https?://[a-zA-Z0-9@:%._\+~#=/-]+(?:\?\S+)?'
def VALIDATE_URL(url):
if urls.url_parse(url).scheme not in ('http', 'https', 'ftp', 'ftps'):
return 'http://' + url
return url
from odoo import tools, models, fields, api, _
class LinkTracker(models.Model):
@@ -101,7 +87,7 @@ class LinkTracker(models.Model):
if 'url' not in create_vals:
raise ValueError('URL field required')
else:
create_vals['url'] = VALIDATE_URL(vals['url'])
create_vals['url'] = tools.validate_url(vals['url'])
search_domain = []
for fname, value in create_vals.items():
@@ -129,44 +115,10 @@ class LinkTracker(models.Model):
@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
raise NotImplementedError('Moved on mail.render.mixin')
def _convert_links_text(self, body, vals, blacklist=None):
shortened_schema = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/r/'
unsubscribe_schema = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/sms/'
for original_url in re.findall(TEXT_URL_REGEX, body):
# don't shorten already-shortened links or links towards unsubscribe page
if original_url.startswith(shortened_schema) or original_url.startswith(unsubscribe_schema):
continue
# support blacklist items in path, like /u/
parsed = urls.url_parse(original_url, scheme='http')
if blacklist and any(item in parsed.path for item in blacklist):
continue
vals['url'] = utils.unescape(original_url)
link = self.create(vals)
shortened_url = link.short_url
if shortened_url:
body = body.replace(original_url, shortened_url, 1)
return body
raise NotImplementedError('Moved on mail.render.mixin')
def action_view_statistics(self):
action = self.env['ir.actions.act_window'].for_xml_id('link_tracker', 'link_tracker_click_action_statistics')
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from werkzeug import urls, utils
from odoo import api, models, tools
class MailRenderMixin(models.AbstractModel):
_inherit = "mail.render.mixin"
# ------------------------------------------------------------
# TOOLS
# ------------------------------------------------------------
@api.model
def _shorten_links(self, html, link_tracker_vals, blacklist=None, base_url=None):
"""
:param link_tracker_vals: values given to the created link.tracker, containing
for example: campaign_id, medium_id, source_id, and any other relevant fields
like mass_mailing_id in mass_mailing;
:return: update
"""
base_url = base_url or self.env['ir.config_parameter'].sudo().get_param('web.base.url')
short_schema = base_url + '/r/'
for match in re.findall(tools.URL_REGEX, html):
href = match[0]
long_url = match[1]
if not blacklist or not [s for s in blacklist if s in long_url] and not long_url.startswith(short_schema):
create_vals = dict(link_tracker_vals, url=utils.unescape(long_url))
link = self.env['link.tracker'].create(create_vals)
if link.short_url:
new_href = href.replace(long_url, link.short_url)
html = html.replace(href, new_href)
return html
@api.model
def _shorten_links_text(self, html, link_tracker_vals, blacklist=None, base_url=None):
base_url = base_url or self.env['ir.config_parameter'].sudo().get_param('web.base.url')
shortened_schema = base_url + '/r/'
unsubscribe_schema = base_url + '/sms/'
for original_url in re.findall(tools.TEXT_URL_REGEX, html):
# don't shorten already-shortened links or links towards unsubscribe page
if original_url.startswith(shortened_schema) or original_url.startswith(unsubscribe_schema):
continue
# support blacklist items in path, like /u/
parsed = urls.url_parse(original_url, scheme='http')
if blacklist and any(item in parsed.path for item in blacklist):
continue
create_vals = dict(link_tracker_vals, url= utils.unescape(original_url))
link = self.env['link.tracker'].create(create_vals)
if link.short_url:
html = html.replace(original_url, link.short_url, 1)
return html
+1 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api, SUPERUSER_ID
from odoo import fields, models
class UtmCampaign(models.Model):
+1 -3
View File
@@ -6,8 +6,6 @@ import werkzeug.urls
from odoo import api, fields, models, tools
from odoo.addons.link_tracker.models.link_tracker import URL_REGEX
class MailMail(models.Model):
"""Add the mass mailing campaign data to mail"""
@@ -54,7 +52,7 @@ class MailMail(models.Model):
body = super(MailMail, self)._send_prepare_body()
if self.mailing_id and body and self.mailing_trace_ids:
for match in re.findall(URL_REGEX, self.body_html):
for match in re.findall(tools.URL_REGEX, self.body_html):
href = match[0]
url = match[1]
@@ -15,7 +15,7 @@ class MailRenderMixin(models.AbstractModel):
# apply shortener after
if self.env.context.get('post_convert_links'):
for res_id, html in rendered.items():
rendered[res_id] = self.env['link.tracker'].convert_links(
rendered[res_id] = self._shorten_links(
html,
self.env.context['post_convert_links'],
blacklist=['/unsubscribe_from_list']
+1 -1
View File
@@ -561,7 +561,7 @@ class MassMailing(models.Model):
if mass_mailing.medium_id:
vals['medium_id'] = mass_mailing.medium_id.id
res[mass_mailing.id] = self.env['link.tracker'].convert_links(html, vals, blacklist=['/unsubscribe_from_list'])
res[mass_mailing.id] = self._shorten_links(html, vals, blacklist=['/unsubscribe_from_list'])
return res
+2 -4
View File
@@ -3,9 +3,7 @@
import re
from odoo import fields, models
TEXT_URL_REGEX = r'https?://[a-zA-Z0-9@:%._+~#=/-]+'
from odoo import fields, models, tools
class SmsSms(models.Model):
@@ -25,7 +23,7 @@ class SmsSms(models.Model):
continue
body = sms.body
for url in re.findall(TEXT_URL_REGEX, body):
for url in re.findall(tools.TEXT_URL_REGEX, body):
if url.startswith(shortened_schema):
body = body.replace(url, url + '/s/%s' % sms.id)
res[sms.id] = body
@@ -70,7 +70,7 @@ class SMSComposer(models.TransientModel):
if self.mailing_id:
tracker_values = self.mailing_id._get_link_tracker_values()
for sms_id, body in all_bodies.items():
body = self.env['link.tracker'].sudo()._convert_links_text(body, tracker_values)
body = self.env['mail.render.mixin'].sudo()._shorten_links_text(body, tracker_values)
all_bodies[sms_id] = body
return all_bodies
+4 -4
View File
@@ -64,7 +64,7 @@ class TestSMSPost(test_mail_full_common.TestSMSCommon, LinkTrackerMock):
def test_body_link_shorten(self):
link = 'http://www.example.com'
self.env['link.tracker'].search([('url', '=', link)]).unlink()
new_body = self.env['link.tracker']._convert_links_text('Welcome to %s !' % link, self.tracker_values)
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
self.assertNotIn(link, new_body)
self.assertLinkTracker(link, {'utm_campaign': self.utm_c.name, 'utm_medium': self.utm_m.name})
link = self.env['link.tracker'].search([('url', '=', link)])
@@ -72,7 +72,7 @@ class TestSMSPost(test_mail_full_common.TestSMSCommon, LinkTrackerMock):
link = 'https://test.odoo.com/my/super_page?test[0]=42&toto=áâà#title3'
self.env['link.tracker'].search([('url', '=', link)]).unlink()
new_body = self.env['link.tracker']._convert_links_text('Welcome to %s !' % link, self.tracker_values)
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
self.assertNotIn(link, new_body)
self.assertLinkTracker(link, {
'utm_campaign': self.utm_c.name,
@@ -86,14 +86,14 @@ class TestSMSPost(test_mail_full_common.TestSMSCommon, LinkTrackerMock):
def test_body_link_shorten_wshort(self):
link = 'https://test.odoo.com/r/RAOUL'
self.env['link.tracker'].search([('url', '=', link)]).unlink()
new_body = self.env['link.tracker']._convert_links_text('Welcome to %s !' % link, self.tracker_values)
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
self.assertIn(link, new_body)
self.assertFalse(self.env['link.tracker'].search([('url', '=', link)]))
def test_body_link_shorten_wunsubscribe(self):
link = 'https://test.odoo.com/sms/3/'
self.env['link.tracker'].search([('url', '=', link)]).unlink()
new_body = self.env['link.tracker']._convert_links_text('Welcome to %s !' % link, self.tracker_values)
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
self.assertIn(link, new_body)
self.assertFalse(self.env['link.tracker'].search([('url', '=', link)]))
+14 -2
View File
@@ -13,6 +13,7 @@ import time
from email.utils import getaddresses
from lxml import etree
from werkzeug import urls
import odoo
from odoo.loglevels import ustr
@@ -250,9 +251,19 @@ def html_sanitize(src, silent=True, sanitize_tags=True, sanitize_attributes=Fals
return cleaned
#----------------------------------------------------------
# ----------------------------------------------------------
# HTML/Text management
#----------------------------------------------------------
# ----------------------------------------------------------
URL_REGEX = r'(\bhref=[\'"](?!mailto:|tel:|sms:)([^\'"]+)[\'"])'
TEXT_URL_REGEX = r'https?://[a-zA-Z0-9@:%._\+~#=/-]+(?:\?\S+)?'
def validate_url(url):
if urls.url_parse(url).scheme not in ('http', 'https', 'ftp', 'ftps'):
return 'http://' + url
return url
def is_html_empty(html_content):
@@ -281,6 +292,7 @@ def html_keep_url(text):
final += text[idx:]
return final
def html2plaintext(html, body_id=None, encoding='utf-8'):
""" From an HTML text, convert the HTML to plain text.
If @param body_id is provided then this is the tag where the