utm_mixin.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import re
  4. from collections import defaultdict
  5. from odoo import api, fields, models
  6. from odoo.http import request
  7. from odoo.osv import expression
  8. class UtmMixin(models.AbstractModel):
  9. """ Mixin class for objects which can be tracked by marketing. """
  10. _name = 'utm.mixin'
  11. _description = 'UTM Mixin'
  12. campaign_id = fields.Many2one('utm.campaign', 'Campaign',
  13. help="This is a name that helps you keep track of your different campaign efforts, e.g. Fall_Drive, Christmas_Special")
  14. source_id = fields.Many2one('utm.source', 'Source',
  15. help="This is the source of the link, e.g. Search Engine, another domain, or name of email list")
  16. medium_id = fields.Many2one('utm.medium', 'Medium',
  17. help="This is the method of delivery, e.g. Postcard, Email, or Banner Ad")
  18. @api.model
  19. def default_get(self, fields):
  20. values = super(UtmMixin, self).default_get(fields)
  21. # We ignore UTM for salesmen, except some requests that could be done as superuser_id to bypass access rights.
  22. if not self.env.is_superuser() and self.env.user.has_group('sales_team.group_sale_salesman'):
  23. return values
  24. for url_param, field_name, cookie_name in self.env['utm.mixin'].tracking_fields():
  25. if field_name in fields:
  26. field = self._fields[field_name]
  27. value = False
  28. if request:
  29. # ir_http dispatch saves the url params in a cookie
  30. value = request.httprequest.cookies.get(cookie_name)
  31. # if we receive a string for a many2one, we search/create the id
  32. if field.type == 'many2one' and isinstance(value, str) and value:
  33. record = self._find_or_create_record(field.comodel_name, value)
  34. value = record.id
  35. if value:
  36. values[field_name] = value
  37. return values
  38. def tracking_fields(self):
  39. # This function cannot be overridden in a model which inherit utm.mixin
  40. # Limitation by the heritage on AbstractModel
  41. # record_crm_lead.tracking_fields() will call tracking_fields() from module utm.mixin (if not overridden on crm.lead)
  42. # instead of the overridden method from utm.mixin.
  43. # To force the call of overridden method, we use self.env['utm.mixin'].tracking_fields() which respects overridden
  44. # methods of utm.mixin, but will ignore overridden method on crm.lead
  45. return [
  46. # ("URL_PARAMETER", "FIELD_NAME_MIXIN", "NAME_IN_COOKIES")
  47. ('utm_campaign', 'campaign_id', 'odoo_utm_campaign'),
  48. ('utm_source', 'source_id', 'odoo_utm_source'),
  49. ('utm_medium', 'medium_id', 'odoo_utm_medium'),
  50. ]
  51. def _find_or_create_record(self, model_name, name):
  52. """Based on the model name and on the name of the record, retrieve the corresponding record or create it."""
  53. Model = self.env[model_name]
  54. record = Model.search([('name', '=', name)], limit=1)
  55. if not record:
  56. # No record found, create a new one
  57. record_values = {'name': name}
  58. if 'is_auto_campaign' in record._fields:
  59. record_values['is_auto_campaign'] = True
  60. record = Model.create(record_values)
  61. return record
  62. @api.model
  63. def _get_unique_names(self, model_name, names):
  64. """Generate unique names for the given model.
  65. Take a list of names and return for each names, the new names to set
  66. in the same order (with a counter added if needed).
  67. E.G.
  68. The name "test" already exists in database
  69. Input: ['test', 'test [3]', 'bob', 'test', 'test']
  70. Output: ['test [2]', 'test [3]', 'bob', 'test [4]', 'test [5]']
  71. :param model_name: name of the model for which we will generate unique names
  72. :param names: list of names, we will ensure that each name will be unique
  73. :return: a list of new values for each name, in the same order
  74. """
  75. def _split_name_and_count(name):
  76. """
  77. Return the name part and the counter based on the given name.
  78. e.g.
  79. "Medium" -> "Medium", 1
  80. "Medium [1234]" -> "Medium", 1234
  81. """
  82. name = name or ''
  83. name_counter_re = r'(.*)\s+\[([0-9]+)\]'
  84. match = re.match(name_counter_re, name)
  85. if match:
  86. return match.group(1), int(match.group(2) or '1')
  87. return name, 1
  88. # Remove potential counter part in each names
  89. names_without_counter = {_split_name_and_count(name)[0] for name in names}
  90. # Retrieve existing similar names
  91. seach_domain = expression.OR([[('name', 'ilike', name)] for name in names_without_counter])
  92. existing_names = {vals['name'] for vals in self.env[model_name].search_read(seach_domain, ['name'])}
  93. # Count for each names, based on the names list given in argument
  94. # and the record names in database
  95. count_per_names = defaultdict(lambda: 0)
  96. count_per_names.update({
  97. name: max((
  98. _split_name_and_count(existing_name)[1] + 1
  99. for existing_name in existing_names
  100. if existing_name == name or existing_name.startswith(f'{name} [')
  101. ), default=1)
  102. for name in names_without_counter
  103. })
  104. result = []
  105. for name in names:
  106. if not name:
  107. result.append(False)
  108. continue
  109. name_without_counter = _split_name_and_count(name)[0]
  110. counter = count_per_names[name_without_counter]
  111. result.append(f'{name_without_counter} [{counter}]' if counter > 1 else name)
  112. count_per_names[name_without_counter] += 1
  113. return result