mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 00:12:04 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
|
|
@ -17,7 +17,7 @@ class IrHttp(models.AbstractModel):
|
|||
response = Response.load(response)
|
||||
domain = cls.get_utm_domain_cookies()
|
||||
for url_parameter, __, cookie_name in request.env['utm.mixin'].tracking_fields():
|
||||
if url_parameter in request.params and request.httprequest.cookies.get(cookie_name) != request.params[url_parameter]:
|
||||
if url_parameter in request.params and request.cookies.get(cookie_name) != request.params[url_parameter]:
|
||||
response.set_cookie(cookie_name, request.params[url_parameter], max_age=31 * 24 * 3600, domain=domain, cookie_type='optional')
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -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, api
|
||||
|
||||
|
||||
class UtmCampaign(models.Model):
|
||||
|
|
@ -9,6 +9,7 @@ class UtmCampaign(models.Model):
|
|||
_description = 'UTM Campaign'
|
||||
_rec_name = 'title'
|
||||
|
||||
active = fields.Boolean('Active', default=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)
|
||||
|
|
@ -19,7 +20,7 @@ class UtmCampaign(models.Model):
|
|||
stage_id = fields.Many2one(
|
||||
'utm.stage', string='Stage', ondelete='restrict', required=True,
|
||||
default=lambda self: self.env['utm.stage'].search([], limit=1),
|
||||
group_expand='_group_expand_stage_ids')
|
||||
copy=False, group_expand='_group_expand_stage_ids')
|
||||
tag_ids = fields.Many2many(
|
||||
'utm.tag', 'utm_tag_rel',
|
||||
'tag_id', 'campaign_id', string='Tags')
|
||||
|
|
@ -27,9 +28,10 @@ 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'),
|
||||
]
|
||||
_unique_name = models.Constraint(
|
||||
'UNIQUE(name)',
|
||||
'The name must be unique',
|
||||
)
|
||||
|
||||
@api.depends('title')
|
||||
def _compute_name(self):
|
||||
|
|
@ -51,9 +53,9 @@ class UtmCampaign(models.Model):
|
|||
return super().create(vals_list)
|
||||
|
||||
@api.model
|
||||
def _group_expand_stage_ids(self, stages, domain, order):
|
||||
def _group_expand_stage_ids(self, stages, domain):
|
||||
"""Read group customization in order to display all the stages in the
|
||||
Kanban view, even if they are empty.
|
||||
"""
|
||||
stage_ids = stages._search([], order=order, access_rights_uid=SUPERUSER_ID)
|
||||
stage_ids = stages.sudo()._search([], order=stages._order)
|
||||
return stages.browse(stage_ids)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class UtmMedium(models.Model):
|
||||
_name = 'utm.medium'
|
||||
|
|
@ -14,9 +16,10 @@ class UtmMedium(models.Model):
|
|||
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'),
|
||||
]
|
||||
_unique_name = models.Constraint(
|
||||
'UNIQUE(name)',
|
||||
'The name must be unique',
|
||||
)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
|
@ -25,12 +28,40 @@ class UtmMedium(models.Model):
|
|||
vals['name'] = new_name
|
||||
return super().create(vals_list)
|
||||
|
||||
@property
|
||||
def SELF_REQUIRED_UTM_MEDIUMS_REF(self):
|
||||
return {
|
||||
'utm.utm_medium_email': 'Email',
|
||||
'utm.utm_medium_direct': 'Direct',
|
||||
'utm.utm_medium_website': 'Website',
|
||||
'utm.utm_medium_twitter': 'X',
|
||||
'utm.utm_medium_facebook': 'Facebook',
|
||||
'utm.utm_medium_linkedin': 'LinkedIn'
|
||||
}
|
||||
|
||||
@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)
|
||||
if utm_medium_email and utm_medium_email in self:
|
||||
raise UserError(_(
|
||||
"The UTM medium '%s' cannot be deleted as it is used in some main "
|
||||
"functional flows, such as the recruitment and the mass mailing.",
|
||||
utm_medium_email.name
|
||||
))
|
||||
def _unlink_except_utm_medium_record(self):
|
||||
for medium in self.SELF_REQUIRED_UTM_MEDIUMS_REF:
|
||||
utm_medium = self.env.ref(medium, raise_if_not_found=False)
|
||||
if utm_medium and utm_medium in self:
|
||||
raise UserError(_(
|
||||
"Oops, you can't delete the Medium '%s'.\n"
|
||||
"Doing so would be like tearing down a load-bearing wall \u2014 not the best idea.",
|
||||
utm_medium.name
|
||||
))
|
||||
|
||||
def _fetch_or_create_utm_medium(self, name, module='utm'):
|
||||
name_normalized = re.sub(r"[\s|.]", "_", name.lower())
|
||||
try:
|
||||
return self.env.ref(f'{module}.utm_medium_{name_normalized}')
|
||||
except ValueError:
|
||||
utm_medium = self.sudo().env['utm.medium'].create({
|
||||
'name': self.SELF_REQUIRED_UTM_MEDIUMS_REF.get(f'{module}.utm_medium_{name_normalized}', name)
|
||||
})
|
||||
self.sudo().env['ir.model.data'].create({
|
||||
'name': f'utm_medium_{name_normalized}',
|
||||
'module': module,
|
||||
'res_id': utm_medium.id,
|
||||
'model': 'utm.medium',
|
||||
})
|
||||
return utm_medium
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from collections import defaultdict
|
|||
import itertools
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.fields import Domain
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class UtmMixin(models.AbstractModel):
|
||||
|
|
@ -15,11 +15,11 @@ class UtmMixin(models.AbstractModel):
|
|||
_name = 'utm.mixin'
|
||||
_description = 'UTM Mixin'
|
||||
|
||||
campaign_id = fields.Many2one('utm.campaign', 'Campaign',
|
||||
campaign_id = fields.Many2one('utm.campaign', 'Campaign', index='btree_not_null',
|
||||
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',
|
||||
source_id = fields.Many2one('utm.source', 'Source', index='btree_not_null',
|
||||
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',
|
||||
medium_id = fields.Many2one('utm.medium', 'Medium', index='btree_not_null',
|
||||
help="This is the method of delivery, e.g. Postcard, Email, or Banner Ad")
|
||||
|
||||
@api.model
|
||||
|
|
@ -30,13 +30,13 @@ class UtmMixin(models.AbstractModel):
|
|||
if not self.env.is_superuser() 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():
|
||||
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)
|
||||
value = request.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:
|
||||
record = self._find_or_create_record(field.comodel_name, value)
|
||||
|
|
@ -59,15 +59,43 @@ class UtmMixin(models.AbstractModel):
|
|||
('utm_medium', 'medium_id', 'odoo_utm_medium'),
|
||||
]
|
||||
|
||||
def _tracking_models(self):
|
||||
fnames = {fname for _, fname, _ in self.tracking_fields()}
|
||||
return {
|
||||
self._fields[fname].comodel_name
|
||||
for fname in fnames
|
||||
if fname in self._fields and self._fields[fname].type == "many2one"
|
||||
}
|
||||
|
||||
@api.model
|
||||
def find_or_create_record(self, model_name, name):
|
||||
""" Version of ``_find_or_create_record`` used in frontend notably in
|
||||
website_links. For UTM models it calls _find_or_create_record. For other
|
||||
models (as through inheritance custom models could be used notably in
|
||||
website links) it simply calls a create. In the end it relies on
|
||||
standard ACLs, and is mainly a wrapper for UTM models.
|
||||
|
||||
:return: id of newly created or found record. As the magic of call_kw
|
||||
for create is not called anymore we have to manually return an id
|
||||
instead of a recordset.
|
||||
"""
|
||||
if model_name in self._tracking_models():
|
||||
record = self._find_or_create_record(model_name, name)
|
||||
else:
|
||||
record = self.env[model_name].create({self.env[model_name]._rec_name: name})
|
||||
return {'id': record.id, 'name': record.display_name}
|
||||
|
||||
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)
|
||||
cleaned_name = name.strip()
|
||||
if cleaned_name:
|
||||
record = Model.with_context(active_test=False).search([('name', '=ilike', cleaned_name)], limit=1)
|
||||
|
||||
if not record:
|
||||
# No record found, create a new one
|
||||
record_values = {'name': name}
|
||||
record_values = {'name': cleaned_name}
|
||||
if 'is_auto_campaign' in record._fields:
|
||||
record_values['is_auto_campaign'] = True
|
||||
record = Model.create(record_values)
|
||||
|
|
@ -97,12 +125,9 @@ class UtmMixin(models.AbstractModel):
|
|||
names_without_counter = {self._split_name_and_count(name)[0] for name in names}
|
||||
|
||||
# Retrieve existing similar names
|
||||
search_domain = expression.OR([[('name', 'ilike', name)] for name in names_without_counter])
|
||||
search_domain = Domain.OR(Domain('name', 'ilike', name) for name in names_without_counter)
|
||||
if skip_record_ids:
|
||||
search_domain = expression.AND([
|
||||
[('id', 'not in', skip_record_ids)],
|
||||
search_domain
|
||||
])
|
||||
search_domain &= Domain('id', 'not in', skip_record_ids)
|
||||
existing_names = {vals['name'] for vals in self.env[model_name].search_read(search_domain, ['name'])}
|
||||
|
||||
# Counter for each names, based on the names list given in argument
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
from odoo import _, api, fields, models, tools
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class UtmSource(models.Model):
|
||||
|
|
@ -11,9 +10,17 @@ class UtmSource(models.Model):
|
|||
|
||||
name = fields.Char(string='Source Name', required=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_name', 'UNIQUE(name)', 'The name must be unique'),
|
||||
]
|
||||
_unique_name = models.Constraint(
|
||||
'UNIQUE(name)',
|
||||
'The name must be unique',
|
||||
)
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_referral(self):
|
||||
utm_source_referral = self.env.ref('utm.utm_source_referral', raise_if_not_found=False)
|
||||
for record in self:
|
||||
if record == utm_source_referral:
|
||||
raise ValidationError(_("You cannot delete the 'Referral' UTM source record."))
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
|
@ -31,12 +38,13 @@ class UtmSource(models.Model):
|
|||
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)
|
||||
create_date = record.create_date or fields.Datetime.today()
|
||||
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,
|
||||
content=content,
|
||||
model_description=model_description,
|
||||
create_date=fields.Date.to_string(create_date),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -51,9 +59,9 @@ class UtmSourceMixin(models.AbstractModel):
|
|||
source_id = fields.Many2one('utm.source', string='Source', required=True, ondelete='restrict', copy=False)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
# Exclude 'name' from fields_list to avoid retrieving it from context.
|
||||
return super().default_get([field for field in fields_list if field != "name"])
|
||||
def default_get(self, fields):
|
||||
# Exclude 'name' from fields to avoid retrieving it from context.
|
||||
return super().default_get([field for field in fields if field != "name"])
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
|
@ -80,23 +88,26 @@ class UtmSourceMixin(models.AbstractModel):
|
|||
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, values):
|
||||
if (values.get(self._rec_name) or values.get('name')) and len(self) > 1:
|
||||
def write(self, vals):
|
||||
if (vals.get(self._rec_name) or vals.get('name')) and len(self) > 1:
|
||||
raise ValueError(
|
||||
_('You cannot update multiple records with the same name. The name should be unique!')
|
||||
)
|
||||
|
||||
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'].with_context(
|
||||
if vals.get(self._rec_name) and not vals.get('name'):
|
||||
vals['name'] = self.env['utm.source']._generate_name(self, vals[self._rec_name])
|
||||
if vals.get('name'):
|
||||
vals['name'] = self.env['utm.mixin'].with_context(
|
||||
utm_check_skip_record_ids=self.source_id.ids
|
||||
)._get_unique_names("utm.source", [values['name']])[0]
|
||||
)._get_unique_names("utm.source", [vals['name']])[0]
|
||||
|
||||
super().write(values)
|
||||
return super().write(vals)
|
||||
|
||||
def copy(self, default=None):
|
||||
def copy_data(self, default=None):
|
||||
"""Increment the counter when duplicating the source."""
|
||||
default = default or {}
|
||||
default['name'] = self.env['utm.mixin']._get_unique_names("utm.source", [self.name])[0]
|
||||
return super().copy(default)
|
||||
default_name = default.get('name')
|
||||
vals_list = super().copy_data(default=default)
|
||||
for source, vals in zip(self, vals_list):
|
||||
vals['name'] = self.env['utm.mixin']._get_unique_names("utm.source", [default_name or source.name])[0]
|
||||
return vals_list
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class UtmTag(models.Model):
|
|||
string='Color Index', default=lambda self: self._default_color(),
|
||||
help='Tag color. No color means no display in kanban to distinguish internal tags from public categorization tags.')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', "Tag name already exists !"),
|
||||
]
|
||||
_name_uniq = models.Constraint(
|
||||
'unique (name)',
|
||||
'Tag name already exists!',
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue