[IMP] utm: ensure the uniqueness of the names of the UTM models
Purpose ======= Ensure that the names of the UTMs models (campaign, medium and source) are unique. If not, generate automatically an unique name. The name field is no more translatable; it makes no sense to translate a technical which will be added in the URL, and can even cause issues (the UTM record is not recognized because of the translation). For the campaign, we keep a "translatable" name which is called "title". For the UTM source, use a mixin to generate automatically the name of the source based on the content (_rec_name) of the record. So we remove the override of create / write / copy in the different models and ensure consistency between those models. Task-2245823 closes odoo/odoo#60501 Related: odoo/enterprise#21882 Related: odoo/upgrade#1863 Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
This commit is contained in:
@@ -21,9 +21,8 @@ AVAILABLE_PRIORITIES = [
|
||||
class RecruitmentSource(models.Model):
|
||||
_name = "hr.recruitment.source"
|
||||
_description = "Source of Applicants"
|
||||
_inherits = {"utm.source": "source_id"}
|
||||
_inherit = ['utm.source.mixin']
|
||||
|
||||
source_id = fields.Many2one('utm.source', "Source", ondelete='restrict', required=True)
|
||||
email = fields.Char(related='alias_id.display_name', string="Email", readonly=True)
|
||||
job_id = fields.Many2one('hr.job', "Job", ondelete='cascade')
|
||||
alias_id = fields.Many2one('mail.alias', "Alias ID")
|
||||
|
||||
@@ -30,9 +30,8 @@ class MassMailing(models.Model):
|
||||
A mass mailing is an occurence of sending emails. """
|
||||
_name = 'mailing.mailing'
|
||||
_description = 'Mass Mailing'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'mail.render.mixin']
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'mail.render.mixin', 'utm.source.mixin']
|
||||
_order = 'calendar_date DESC'
|
||||
_inherits = {'utm.source': 'source_id'}
|
||||
_rec_name = "subject"
|
||||
|
||||
@api.model
|
||||
@@ -93,8 +92,6 @@ class MassMailing(models.Model):
|
||||
'mass_mailing_id', 'attachment_id', string='Attachments')
|
||||
keep_archives = fields.Boolean(string='Keep Archives')
|
||||
campaign_id = fields.Many2one('utm.campaign', string='UTM Campaign', index=True, ondelete='set null')
|
||||
source_id = fields.Many2one('utm.source', string='Source', required=True, ondelete='restrict',
|
||||
help="This is the link source, e.g. Search Engine, another domain, or name of email list")
|
||||
medium_id = fields.Many2one(
|
||||
'utm.medium', string='Medium',
|
||||
compute='_compute_medium_id', readonly=False, store=True, ondelete='restrict',
|
||||
@@ -400,11 +397,8 @@ class MassMailing(models.Model):
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
now = fields.Datetime.now()
|
||||
ab_testing_cron = self.env.ref('mass_mailing.ir_cron_mass_mailing_ab_testing').sudo()
|
||||
for values in vals_list:
|
||||
if values.get('subject') and not values.get('name'):
|
||||
values['name'] = "%s %s" % (values['subject'], now)
|
||||
if values.get('body_html'):
|
||||
values['body_html'] = self._convert_inline_images_to_urls(values['body_html'])
|
||||
if values.get('ab_testing_schedule_datetime'):
|
||||
@@ -451,9 +445,7 @@ class MassMailing(models.Model):
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def copy(self, default=None):
|
||||
self.ensure_one()
|
||||
default = dict(default or {},
|
||||
name=_('%s (copy)', self.name),
|
||||
contact_list_ids=self.contact_list_ids.ids)
|
||||
default = dict(default or {}, contact_list_ids=self.contact_list_ids.ids)
|
||||
return super(MassMailing, self).copy(default=default)
|
||||
|
||||
def _group_expand_states(self, states, domain, order):
|
||||
|
||||
@@ -5,10 +5,12 @@ from ast import literal_eval
|
||||
from datetime import datetime
|
||||
from freezegun import freeze_time
|
||||
from psycopg2 import IntegrityError
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
|
||||
from odoo.addons.mass_mailing.tests.common import MassMailCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.sql_db import Cursor
|
||||
from odoo.tests.common import users, Form
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
@@ -227,6 +229,50 @@ class TestMassMailValues(MassMailCommon):
|
||||
activity.write({'res_id': 0})
|
||||
activity.flush()
|
||||
|
||||
@freeze_time('2022-01-02')
|
||||
@patch.object(Cursor, 'now', lambda *args, **kwargs: datetime(2022, 1, 2))
|
||||
@users('user_marketing')
|
||||
def test_mailing_unique_name(self):
|
||||
"""Test that the names are generated and unique for each mailing.
|
||||
|
||||
If the name is missing, it's generated from the subject. Then we should ensure
|
||||
that this generated name is unique.
|
||||
"""
|
||||
mailing_0 = self.env['mailing.mailing'].create({'subject': 'First subject'})
|
||||
|
||||
mailing_1, mailing_2, mailing_3, mailing_4, mailing_5, mailing_6 = self.env['mailing.mailing'].create([{
|
||||
'subject': 'First subject',
|
||||
}, {
|
||||
'subject': 'First subject',
|
||||
}, {
|
||||
'subject': 'First subject',
|
||||
'source_id': self.env['utm.source'].create({'name': 'Custom Source'}).id,
|
||||
}, {
|
||||
'subject': 'First subject',
|
||||
'name': 'Mailing',
|
||||
}, {
|
||||
'subject': 'Second subject',
|
||||
'name': 'Mailing',
|
||||
}, {
|
||||
'subject': 'Second subject',
|
||||
}])
|
||||
|
||||
self.assertEqual(mailing_0.name, 'First subject (Mass Mailing created on 2022-01-02)')
|
||||
self.assertEqual(mailing_1.name, 'First subject (Mass Mailing created on 2022-01-02) [2]')
|
||||
self.assertEqual(mailing_2.name, 'First subject (Mass Mailing created on 2022-01-02) [3]')
|
||||
self.assertEqual(mailing_3.name, 'Custom Source')
|
||||
self.assertEqual(mailing_4.name, 'Mailing')
|
||||
self.assertEqual(mailing_5.name, 'Mailing [2]')
|
||||
self.assertEqual(mailing_6.name, 'Second subject (Mass Mailing created on 2022-01-02)')
|
||||
|
||||
mailing_0.subject = 'First subject'
|
||||
self.assertEqual(mailing_0.name, 'First subject (Mass Mailing created on 2022-01-02) [4]',
|
||||
msg='The name must have been re-generated')
|
||||
|
||||
mailing_0.name = 'Second subject (Mass Mailing created on 2022-01-02)'
|
||||
self.assertEqual(mailing_0.name, 'Second subject (Mass Mailing created on 2022-01-02) [2]',
|
||||
msg='The name must be unique')
|
||||
|
||||
|
||||
class TestMassMailFeatures(MassMailCommon, CronMixinCase):
|
||||
|
||||
|
||||
@@ -121,14 +121,17 @@
|
||||
<value name="mailing_trace_id" eval="ref('mailing_sms_0_trace_2')"/>
|
||||
</function>
|
||||
|
||||
<record id="mailing_sms_0_utm_source" model="utm.source">
|
||||
<field name="name">Mailing Extra Promo</field>
|
||||
</record>
|
||||
<record id="mailing_sms_1" model="mailing.mailing">
|
||||
<field name="name">Extra Promo</field>
|
||||
<field name="subject">Extra Promo</field>
|
||||
<field name="mailing_type">sms</field>
|
||||
<field name="state">done</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="sent_date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="campaign_id" ref="utm_campaign_0"/>
|
||||
<field name="source_id" ref="mailing_sms_0_utm_source"/>
|
||||
<field name="mailing_model_id" ref="mass_mailing.model_mailing_list"/>
|
||||
<field name="contact_list_ids" eval="[(5, 0), (4, ref('mass_mailing_sms.mailing_list_sms_0'))]"/>
|
||||
<field name="body_plaintext">Extra promotion for you !</field>
|
||||
|
||||
@@ -7,8 +7,11 @@ from odoo import fields, models, api, SUPERUSER_ID
|
||||
class UtmCampaign(models.Model):
|
||||
_name = 'utm.campaign'
|
||||
_description = 'UTM Campaign'
|
||||
_rec_name = 'title'
|
||||
|
||||
name = fields.Char(string='Campaign Name', required=True, translate=True)
|
||||
name = fields.Char(string='Campaign Identifier', required=True, compute='_compute_name',
|
||||
store=True, readonly=False, precompute=True, translate=False)
|
||||
title = fields.Char(string='Campaign Name', required=True, translate=True)
|
||||
|
||||
user_id = fields.Many2one(
|
||||
'res.users', string='Responsible',
|
||||
@@ -24,6 +27,26 @@ class UtmCampaign(models.Model):
|
||||
is_auto_campaign = fields.Boolean(default=False, string="Automatically Generated Campaign", help="Allows us to filter relevant Campaigns")
|
||||
color = fields.Integer(string='Color Index')
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_name', 'UNIQUE(name)', 'The name must be unique'),
|
||||
]
|
||||
|
||||
@api.depends('title')
|
||||
def _compute_name(self):
|
||||
new_names = self.env['utm.mixin']._get_unique_names(self._name, [c.title for c in self])
|
||||
for campaign, new_name in zip(self, new_names):
|
||||
campaign.name = new_name
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if not vals.get('title') and vals.get('name'):
|
||||
vals['title'] = vals['name']
|
||||
new_names = self.env['utm.mixin']._get_unique_names(self._name, [vals.get('name') for vals in vals_list])
|
||||
for vals, new_name in zip(vals_list, new_names):
|
||||
vals['name'] = new_name
|
||||
return super().create(vals_list)
|
||||
|
||||
@api.model
|
||||
def _group_expand_stage_ids(self, stages, domain, order):
|
||||
"""Read group customization in order to display all the stages in the
|
||||
|
||||
@@ -11,9 +11,20 @@ class UtmMedium(models.Model):
|
||||
_description = 'UTM Medium'
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char(string='Medium Name', required=True, translate=True)
|
||||
name = fields.Char(string='Medium Name', required=True, translate=False)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_name', 'UNIQUE(name)', 'The name must be unique'),
|
||||
]
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
new_names = self.env['utm.mixin']._get_unique_names(self._name, [vals.get('name') for vals in vals_list])
|
||||
for vals, new_name in zip(vals_list, new_names):
|
||||
vals['name'] = new_name
|
||||
return super().create(vals_list)
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_utm_medium_email(self):
|
||||
utm_medium_email = self.env.ref('utm.utm_medium_email', raise_if_not_found=False)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class UtmMixin(models.AbstractModel):
|
||||
@@ -34,14 +38,8 @@ class UtmMixin(models.AbstractModel):
|
||||
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, str) and value:
|
||||
Model = self.env[field.comodel_name]
|
||||
records = Model.search([('name', '=', value)], limit=1)
|
||||
if not records:
|
||||
if 'is_auto_campaign' in records._fields:
|
||||
records = Model.create({'name': value, 'is_auto_campaign': True})
|
||||
else:
|
||||
records = Model.create({'name': value})
|
||||
value = records.id
|
||||
record = self._find_or_create_record(field.comodel_name, value)
|
||||
value = record.id
|
||||
if value:
|
||||
values[field_name] = value
|
||||
return values
|
||||
@@ -59,3 +57,81 @@ class UtmMixin(models.AbstractModel):
|
||||
('utm_source', 'source_id', 'odoo_utm_source'),
|
||||
('utm_medium', 'medium_id', 'odoo_utm_medium'),
|
||||
]
|
||||
|
||||
def _find_or_create_record(self, model_name, name):
|
||||
"""Based on the model name and on the name of the record, retrieve the corresponding record or create it."""
|
||||
Model = self.env[model_name]
|
||||
|
||||
record = Model.search([('name', '=', name)], limit=1)
|
||||
|
||||
if not record:
|
||||
# No record found, create a new one
|
||||
record_values = {'name': name}
|
||||
if 'is_auto_campaign' in record._fields:
|
||||
record_values['is_auto_campaign'] = True
|
||||
record = Model.create(record_values)
|
||||
|
||||
return record
|
||||
|
||||
@api.model
|
||||
def _get_unique_names(self, model_name, names):
|
||||
"""Generate unique names for the given model.
|
||||
|
||||
Take a list of names and return for each names, the new names to set
|
||||
in the same order (with a counter added if needed).
|
||||
|
||||
E.G.
|
||||
The name "test" already exists in database
|
||||
Input: ['test', 'test [3]', 'bob', 'test', 'test']
|
||||
Output: ['test [2]', 'test [3]', 'bob', 'test [4]', 'test [5]']
|
||||
|
||||
:param model_name: name of the model for which we will generate unique names
|
||||
:param names: list of names, we will ensure that each name will be unique
|
||||
:return: a list of new values for each name, in the same order
|
||||
"""
|
||||
def _split_name_and_count(name):
|
||||
"""
|
||||
Return the name part and the counter based on the given name.
|
||||
|
||||
e.g.
|
||||
"Medium" -> "Medium", 1
|
||||
"Medium [1234]" -> "Medium", 1234
|
||||
"""
|
||||
name = name or ''
|
||||
name_counter_re = r'(.*)\s+\[([0-9]+)\]'
|
||||
match = re.match(name_counter_re, name)
|
||||
if match:
|
||||
return match.group(1), int(match.group(2) or '1')
|
||||
return name, 1
|
||||
|
||||
# Remove potential counter part in each names
|
||||
names_without_counter = {_split_name_and_count(name)[0] for name in names}
|
||||
|
||||
# Retrieve existing similar names
|
||||
seach_domain = expression.OR([[('name', 'ilike', name)] for name in names_without_counter])
|
||||
existing_names = {vals['name'] for vals in self.env[model_name].search_read(seach_domain, ['name'])}
|
||||
|
||||
# Count for each names, based on the names list given in argument
|
||||
# and the record names in database
|
||||
count_per_names = defaultdict(lambda: 0)
|
||||
count_per_names.update({
|
||||
name: max((
|
||||
_split_name_and_count(existing_name)[1] + 1
|
||||
for existing_name in existing_names
|
||||
if existing_name == name or existing_name.startswith(f'{name} [')
|
||||
), default=1)
|
||||
for name in names_without_counter
|
||||
})
|
||||
|
||||
result = []
|
||||
for name in names:
|
||||
if not name:
|
||||
result.append(False)
|
||||
continue
|
||||
|
||||
name_without_counter = _split_name_and_count(name)[0]
|
||||
counter = count_per_names[name_without_counter]
|
||||
result.append(f'{name_without_counter} [{counter}]' if counter > 1 else name)
|
||||
count_per_names[name_without_counter] += 1
|
||||
|
||||
return result
|
||||
|
||||
@@ -2,11 +2,85 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import _, api, fields, models, tools
|
||||
|
||||
|
||||
class UtmSource(models.Model):
|
||||
_name = 'utm.source'
|
||||
_description = 'UTM Source'
|
||||
|
||||
name = fields.Char(string='Source Name', required=True, translate=True)
|
||||
name = fields.Char(string='Source Name', required=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_name', 'UNIQUE(name)', 'The name must be unique'),
|
||||
]
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
new_names = self.env['utm.mixin']._get_unique_names(self._name, [vals.get('name') for vals in vals_list])
|
||||
for vals, new_name in zip(vals_list, new_names):
|
||||
vals['name'] = new_name
|
||||
return super().create(vals_list)
|
||||
|
||||
def _generate_name(self, record, content):
|
||||
"""Generate the UTM source name based on the content of the source."""
|
||||
if not content:
|
||||
return False
|
||||
|
||||
content = content.replace('\n', ' ')
|
||||
if len(content) >= 24:
|
||||
content = f'{content[:20]}...'
|
||||
|
||||
create_date = record.create_date or fields.date.today()
|
||||
create_date = fields.date.strftime(create_date, tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
model_description = self.env['ir.model']._get(record._name).name
|
||||
return _(
|
||||
'%(content)s (%(model_description)s created on %(create_date)s)',
|
||||
content=content, model_description=model_description, create_date=create_date,
|
||||
)
|
||||
|
||||
|
||||
class UtmSourceMixin(models.AbstractModel):
|
||||
"""Mixin responsible of generating the name of the source based on the content
|
||||
(field defined by _rec_name) of the record (mailing, social post,...).
|
||||
"""
|
||||
_name = 'utm.source.mixin'
|
||||
_description = 'UTM Source Mixin'
|
||||
|
||||
name = fields.Char('Name', related='source_id.name', readonly=False)
|
||||
source_id = fields.Many2one('utm.source', string='Source', required=True, ondelete='restrict', copy=False)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
"""Create the UTM sources if necessary, generate the name based on the content in batch."""
|
||||
# Create all required <utm.source>
|
||||
utm_sources = self.env['utm.source'].create([
|
||||
{'name': values.get('name') or self.env['utm.source']._generate_name(self, values.get(self._rec_name))}
|
||||
for values in vals_list
|
||||
if not values.get('source_id')
|
||||
])
|
||||
|
||||
# Update "vals_list" to add the ID of the newly created source
|
||||
vals_list_missing_source = [values for values in vals_list if not values.get('source_id')]
|
||||
for values, source in zip(vals_list_missing_source, utm_sources):
|
||||
values['source_id'] = source.id
|
||||
|
||||
for values in vals_list:
|
||||
if 'name' in values:
|
||||
del values['name']
|
||||
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, values):
|
||||
if values.get(self._rec_name) and not values.get('name'):
|
||||
values['name'] = self.env['utm.source']._generate_name(self, values[self._rec_name])
|
||||
if values.get('name'):
|
||||
values['name'] = self.env['utm.mixin']._get_unique_names(self._name, [values['name']])[0]
|
||||
|
||||
super().write(values)
|
||||
|
||||
def copy(self, default=None):
|
||||
"""Increment the counter when duplicating the source."""
|
||||
default = default or {}
|
||||
default['name'] = self.env['utm.mixin']._get_unique_names(self._name, [self.name])[0]
|
||||
return super().copy(default)
|
||||
|
||||
@@ -13,4 +13,4 @@ class UtmStage(models.Model):
|
||||
_order = 'sequence'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
sequence = fields.Integer()
|
||||
sequence = fields.Integer(default=1)
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
|
||||
from . import common
|
||||
from . import test_routes
|
||||
from . import test_utm
|
||||
from . import test_utm_consistency
|
||||
from . import test_utm_security
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.utm.tests.common import TestUTMCommon
|
||||
|
||||
|
||||
class TestUtm(TestUTMCommon):
|
||||
def test_find_or_create_record(self):
|
||||
source_1, source_2 = self.env['utm.source'].create([{
|
||||
'name': 'Source 1',
|
||||
}, {
|
||||
'name': 'Source 2',
|
||||
}])
|
||||
|
||||
# Find the record based on the given name
|
||||
source = self.env['utm.mixin']._find_or_create_record('utm.source', 'Source 1')
|
||||
self.assertEqual(source, source_1)
|
||||
|
||||
# Create a new record
|
||||
source_4 = self.env['utm.mixin']._find_or_create_record('utm.source', 'Source 3')
|
||||
self.assertNotIn(source_4, source_1 | source_2)
|
||||
self.assertEqual(source_4.name, 'Source 3')
|
||||
|
||||
def test_name_generation(self):
|
||||
"""Test that the name is always unique.
|
||||
|
||||
A counter must be added at the end of the name if it's not the case.
|
||||
"""
|
||||
for utm_model in ('utm.source', 'utm.medium', 'utm.campaign'):
|
||||
utm_0 = self.env[utm_model].create({'name': 'UTM dup'})
|
||||
|
||||
utm_1, utm_2, utm_3, utm_4, utm_5 = self.env[utm_model].create([{
|
||||
'name': 'UTM 1',
|
||||
}, {
|
||||
'name': 'UTM 2',
|
||||
}, {
|
||||
'name': 'UTM dup',
|
||||
}, {
|
||||
# UTM record 4 has the same name of the previous UTM record
|
||||
'name': 'UTM dup',
|
||||
}, {
|
||||
# UTM record 5 has the same name of the previous UTM record
|
||||
# but with a wrong counter part, it will be removed and updated
|
||||
'name': 'UTM dup [0]',
|
||||
}])
|
||||
|
||||
self.assertEqual(utm_0.name, 'UTM dup', msg='The first "UTM dup" should be left unchanged since it is unique')
|
||||
self.assertEqual(utm_1.name, 'UTM 1', msg='This name is already unique')
|
||||
self.assertEqual(utm_2.name, 'UTM 2', msg='This name is already unique')
|
||||
self.assertEqual(utm_3.name, 'UTM dup [2]', msg='Must add a counter as suffix to ensure uniqueness')
|
||||
self.assertEqual(utm_4.name, 'UTM dup [3]', msg='Must add a counter as suffix to ensure uniqueness')
|
||||
self.assertEqual(utm_5.name, 'UTM dup [4]', msg='Must add a counter as suffix to ensure uniqueness')
|
||||
|
||||
(utm_0 | utm_3 | utm_4).unlink()
|
||||
|
||||
utm_6 = self.env[utm_model].create({'name': 'UTM dup'})
|
||||
self.assertEqual(utm_6.name, 'UTM dup [5]')
|
||||
|
||||
utm_7 = self.env[utm_model].create({'name': 'UTM d'})
|
||||
self.assertEqual(utm_7.name, 'UTM d', msg='Even if this name has the same prefix as the other, it is still unique')
|
||||
|
||||
utm_8 = utm_7.copy()
|
||||
self.assertEqual(utm_8.name, 'UTM d [2]', msg='Must add a counter as suffix to ensure uniqueness')
|
||||
@@ -17,9 +17,9 @@ class TestUTMSecurity(TestUTMCommon):
|
||||
UtmSource = self.env['utm.source']
|
||||
|
||||
# CREATE
|
||||
test_utm_campaign = UtmCampaign.create({'name': 'Test Campaign'})
|
||||
test_utm_medium = UtmMedium.create({'name': 'Test Medium'})
|
||||
test_utm_source = UtmSource.create({'name': 'Test Source'})
|
||||
test_utm_campaign = UtmCampaign.create({'name': 'Campaign ACLs'})
|
||||
test_utm_medium = UtmMedium.create({'name': 'Medium ACLs'})
|
||||
test_utm_source = UtmSource.create({'name': 'Source ACLs'})
|
||||
|
||||
# READ
|
||||
self.assertEqual(
|
||||
@@ -50,9 +50,9 @@ class TestUTMSecurity(TestUTMCommon):
|
||||
UtmSource = self.env['utm.source']
|
||||
|
||||
# CREATE
|
||||
test_utm_campaign = UtmCampaign.create({'name': 'Test Campaign'})
|
||||
test_utm_medium = UtmMedium.create({'name': 'Test Medium'})
|
||||
test_utm_source = UtmSource.create({'name': 'Test Source'})
|
||||
test_utm_campaign = UtmCampaign.create({'name': 'Campaign ACLs'})
|
||||
test_utm_medium = UtmMedium.create({'name': 'Medium ACLs'})
|
||||
test_utm_source = UtmSource.create({'name': 'Source ACLs'})
|
||||
|
||||
# READ
|
||||
self.assertEqual(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<field name="model">utm.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="UTM Campaigns">
|
||||
<field name="name" string="Campaigns"/>
|
||||
<field name="title" string="Campaigns"/>
|
||||
<field name="tag_ids"/>
|
||||
<field name="user_id"/>
|
||||
<field name="is_auto_campaign"/>
|
||||
@@ -31,7 +31,8 @@
|
||||
<div class="oe_button_box d-flex justify-content-end" name="button_box">
|
||||
</div>
|
||||
<group id="top-group">
|
||||
<field class="o_text_overflow" name="name" string="Campaign Name" placeholder="e.g. Black Friday"/>
|
||||
<field class="o_text_overflow" name="title" string="Campaign Name" placeholder="e.g. Black Friday"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="user_id" domain="[('share', '=', False)]"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}"/>
|
||||
</group>
|
||||
@@ -47,7 +48,8 @@
|
||||
<field name="model">utm.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="UTM Campaigns" multi_edit="1" sample="1">
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="title" readonly="1"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="user_id"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
@@ -62,7 +64,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field class="o_text_overflow" name="name" string="Campaign Name" placeholder="e.g. Black Friday"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field class="o_text_overflow" name="title" string="Campaign Name" placeholder="e.g. Black Friday"/>
|
||||
<field name="user_id" domain="[('share', '=', False)]"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}"/>
|
||||
</group>
|
||||
@@ -99,7 +102,7 @@
|
||||
<div class="oe_kanban_content">
|
||||
<div class="o_kanban_record_top">
|
||||
<div class="o_kanban_record_headings">
|
||||
<h3 class="oe_margin_bottom_8 o_kanban_record_title"><field name="name"/></h3>
|
||||
<h3 class="oe_margin_bottom_8 o_kanban_record_title"><field name="title"/></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_kanban_record_body">
|
||||
|
||||
@@ -8,9 +8,12 @@ import odoo.tests
|
||||
class TestWebsiteCrm(odoo.tests.HttpCase):
|
||||
|
||||
def test_tour(self):
|
||||
all_utm_campaign = self.env['utm.campaign'].search([])
|
||||
utm_medium = self.env['utm.medium'].create({'name': 'Medium'})
|
||||
utm_source = self.env['utm.source'].create({'name': 'Source'})
|
||||
# change action to create opportunity
|
||||
self.start_tour("/", 'website_crm_pre_tour', login='admin')
|
||||
self.start_tour("/", 'website_crm_tour')
|
||||
self.start_tour("/?utm_source=Source&utm_medium=Medium&utm_campaign=New campaign", 'website_crm_tour')
|
||||
|
||||
# check result
|
||||
record = self.env['crm.lead'].search([('description', '=', '### TOUR DATA ###')])
|
||||
@@ -19,6 +22,12 @@ class TestWebsiteCrm(odoo.tests.HttpCase):
|
||||
self.assertEqual(record.email_from, 'john@smith.com')
|
||||
self.assertEqual(record.partner_name, 'Odoo S.A.')
|
||||
|
||||
# check UTM records
|
||||
self.assertEqual(record.source_id, utm_source)
|
||||
self.assertEqual(record.medium_id, utm_medium)
|
||||
self.assertNotIn(record.campaign_id, all_utm_campaign, 'Should have created a new campaign')
|
||||
self.assertEqual(record.campaign_id.name, 'New campaign', 'Name of the "on the fly" created campaign is wrong')
|
||||
|
||||
def test_catch_logged_partner_info_tour(self):
|
||||
user_login = 'admin'
|
||||
user_partner = self.env['res.users'].search([('login', '=', user_login)]).partner_id
|
||||
|
||||
Reference in New Issue
Block a user