odoo-addon-mail-gateway-whatsapp 17.0.1.1.1__py3-none-any.whl → 18.0.1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. odoo/addons/mail_gateway_whatsapp/README.rst +6 -6
  2. odoo/addons/mail_gateway_whatsapp/__manifest__.py +1 -1
  3. odoo/addons/mail_gateway_whatsapp/i18n/es.po +12 -273
  4. odoo/addons/mail_gateway_whatsapp/i18n/it.po +12 -273
  5. odoo/addons/mail_gateway_whatsapp/i18n/mail_gateway_whatsapp.pot +1 -302
  6. odoo/addons/mail_gateway_whatsapp/models/mail_gateway.py +1 -1
  7. odoo/addons/mail_gateway_whatsapp/models/mail_gateway_whatsapp.py +14 -13
  8. odoo/addons/mail_gateway_whatsapp/models/mail_whatsapp_template.py +20 -544
  9. odoo/addons/mail_gateway_whatsapp/security/ir.model.access.csv +0 -4
  10. odoo/addons/mail_gateway_whatsapp/static/description/index.html +4 -4
  11. odoo/addons/mail_gateway_whatsapp/static/src/components/message/message_patch.esm.js +0 -1
  12. odoo/addons/mail_gateway_whatsapp/static/src/components/phone_field/phone_field.esm.js +2 -3
  13. odoo/addons/mail_gateway_whatsapp/static/src/components/send_whatsapp_button/send_whatsapp_button.esm.js +3 -5
  14. odoo/addons/mail_gateway_whatsapp/static/src/components/send_whatsapp_button/send_whatsapp_button.xml +5 -2
  15. odoo/addons/mail_gateway_whatsapp/tests/test_mail_gateway_whatsapp.py +22 -182
  16. odoo/addons/mail_gateway_whatsapp/tests/test_mail_whatsapp_template.py +2 -72
  17. odoo/addons/mail_gateway_whatsapp/tools/const.py +0 -2
  18. odoo/addons/mail_gateway_whatsapp/views/mail_gateway.xml +23 -24
  19. odoo/addons/mail_gateway_whatsapp/views/mail_whatsapp_template_views.xml +4 -60
  20. odoo/addons/mail_gateway_whatsapp/wizards/mail_compose_gateway_message.py +3 -9
  21. odoo/addons/mail_gateway_whatsapp/wizards/mail_compose_gateway_message.xml +0 -1
  22. odoo/addons/mail_gateway_whatsapp/wizards/whatsapp_composer.py +2 -3
  23. odoo/addons/mail_gateway_whatsapp/wizards/whatsapp_composer.xml +1 -4
  24. {odoo_addon_mail_gateway_whatsapp-17.0.1.1.1.dist-info → odoo_addon_mail_gateway_whatsapp-18.0.1.0.1.dist-info}/METADATA +10 -10
  25. {odoo_addon_mail_gateway_whatsapp-17.0.1.1.1.dist-info → odoo_addon_mail_gateway_whatsapp-18.0.1.0.1.dist-info}/RECORD +27 -27
  26. {odoo_addon_mail_gateway_whatsapp-17.0.1.1.1.dist-info → odoo_addon_mail_gateway_whatsapp-18.0.1.0.1.dist-info}/WHEEL +0 -0
  27. {odoo_addon_mail_gateway_whatsapp-17.0.1.1.1.dist-info → odoo_addon_mail_gateway_whatsapp-18.0.1.0.1.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,15 @@
1
1
  # Copyright 2024 Tecnativa - Carlos López
2
2
  # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3
3
  import re
4
- from urllib.parse import urlparse
5
4
 
6
5
  import requests
7
6
  from werkzeug.urls import url_join
8
7
 
9
- from odoo import Command, _, api, fields, models
10
- from odoo.exceptions import UserError, ValidationError
8
+ from odoo import api, fields, models
9
+ from odoo.exceptions import UserError
11
10
  from odoo.tools import ustr
12
11
 
13
- from odoo.addons.http_routing.models.ir_http import slugify
14
- from odoo.addons.phone_validation.tools import phone_validation
15
-
16
- from ..tools.const import REG_VARIABLE, supported_languages
12
+ from ..tools.const import supported_languages
17
13
  from .mail_gateway import BASE_URL
18
14
 
19
15
 
@@ -65,26 +61,6 @@ class MailWhatsAppTemplate(models.Model):
65
61
  company_id = fields.Many2one(
66
62
  "res.company", related="gateway_id.company_id", store=True
67
63
  )
68
- model_id = fields.Many2one(
69
- string="Applies to",
70
- comodel_name="ir.model",
71
- default=lambda self: self.env["ir.model"]._get_id("res.partner"),
72
- required=True,
73
- ondelete="cascade",
74
- )
75
- model = fields.Char(string="Related model", related="model_id.model", store=True)
76
- variable_ids = fields.One2many(
77
- "mail.whatsapp.template.variable",
78
- "template_id",
79
- string="Variables",
80
- store=True,
81
- compute="_compute_variable_ids",
82
- precompute=True,
83
- readonly=False,
84
- )
85
- button_ids = fields.One2many(
86
- "mail.whatsapp.template.button", "template_id", string="Buttons"
87
- )
88
64
 
89
65
  _sql_constraints = [
90
66
  (
@@ -94,67 +70,6 @@ class MailWhatsAppTemplate(models.Model):
94
70
  )
95
71
  ]
96
72
 
97
- @api.constrains("button_ids")
98
- def _check_buttons(self):
99
- for template in self:
100
- if len(template.button_ids) > 10:
101
- raise ValidationError(_("A maximum of 10 buttons is allowed."))
102
- url_buttons = template.button_ids.filtered(
103
- lambda button: button.button_type == "url"
104
- )
105
- phone_number_buttons = template.button_ids.filtered(
106
- lambda button: button.button_type == "phone_number"
107
- )
108
- if len(url_buttons) > 2:
109
- raise ValidationError(_("A maximum of 2 URL buttons is allowed."))
110
- if len(phone_number_buttons) > 1:
111
- raise ValidationError(
112
- _("A maximum of 1 Call Number button is allowed.")
113
- )
114
-
115
- @api.constrains("variable_ids")
116
- def _check_variables(self):
117
- for template in self:
118
- body_variables = template.variable_ids.filtered(
119
- lambda var: var.line_type == "body"
120
- )
121
- header_variables = template.variable_ids.filtered(
122
- lambda var: var.line_type == "header"
123
- )
124
- if len(header_variables) > 1:
125
- raise ValidationError(
126
- _("There should be exactly 1 variable in the header.")
127
- )
128
- if header_variables and header_variables._extract_variable_index() != 1:
129
- raise ValidationError(
130
- _("Variable in the header should be used as {{1}}")
131
- )
132
- variable_indices = sorted(
133
- var._extract_variable_index() for var in body_variables
134
- )
135
- if len(variable_indices) > 0 and (
136
- variable_indices[0] != 1 or variable_indices[-1] != len(body_variables)
137
- ):
138
- missing = (
139
- next(
140
- (
141
- index
142
- for index in range(1, len(body_variables))
143
- if variable_indices[index - 1] + 1
144
- != variable_indices[index]
145
- ),
146
- 0,
147
- )
148
- + 1
149
- )
150
- raise ValidationError(
151
- _(
152
- "Body variables should start at 1 and not skip any number, "
153
- "missing %d",
154
- missing,
155
- )
156
- )
157
-
158
73
  @api.depends("name", "state", "template_uid")
159
74
  def _compute_template_name(self):
160
75
  for template in self:
@@ -162,57 +77,9 @@ class MailWhatsAppTemplate(models.Model):
162
77
  template.state == "draft" and not template.template_uid
163
78
  ):
164
79
  template.template_name = re.sub(
165
- r"\W+", "_", slugify(template.name or "")
80
+ r"\W+", "_", self.env["ir.http"]._slugify(template.name or "")
166
81
  )
167
82
 
168
- @api.depends("header", "body")
169
- def _compute_variable_ids(self):
170
- for template in self:
171
- to_remove = self.env["mail.whatsapp.template.variable"]
172
- to_keep = self.env["mail.whatsapp.template.variable"]
173
- new_values = []
174
- header_variables = list(re.findall(REG_VARIABLE, template.header or ""))
175
- body_variables = set(re.findall(REG_VARIABLE, template.body or ""))
176
- # header
177
- current_header_variable = template.variable_ids.filtered(
178
- lambda line: line.line_type == "header"
179
- )
180
- if header_variables and not current_header_variable:
181
- new_values.append(
182
- {
183
- "name": header_variables[0],
184
- "line_type": "header",
185
- "template_id": template.id,
186
- }
187
- )
188
- elif not header_variables and current_header_variable:
189
- to_remove += current_header_variable
190
- elif current_header_variable:
191
- to_keep += current_header_variable
192
- # body
193
- current_body_variables = template.variable_ids.filtered(
194
- lambda line: line.line_type == "body"
195
- )
196
- new_body_variable_names = [
197
- var_name
198
- for var_name in body_variables
199
- if var_name not in current_body_variables.mapped("name")
200
- ]
201
- deleted_variables = current_body_variables.filtered(
202
- lambda var, body_variables=body_variables: var.name
203
- not in body_variables
204
- )
205
-
206
- new_values += [
207
- {"name": var_name, "line_type": "body", "template_id": template.id}
208
- for var_name in set(new_body_variable_names)
209
- ]
210
- to_remove += deleted_variables
211
- to_keep += current_body_variables - deleted_variables
212
- template.variable_ids = [(3, to_remove.id) for to_remove in to_remove] + [
213
- Command.create(vals) for vals in new_values
214
- ]
215
-
216
83
  def button_back2draft(self):
217
84
  self.write({"state": "draft"})
218
85
 
@@ -227,7 +94,7 @@ class MailWhatsAppTemplate(models.Model):
227
94
  payload = self._prepare_values_to_export()
228
95
  response = requests.post(
229
96
  template_url,
230
- headers={"Authorization": "Bearer %s" % gateway.token},
97
+ headers={"Authorization": f"Bearer {gateway.token}"},
231
98
  json=payload,
232
99
  timeout=10,
233
100
  )
@@ -256,39 +123,23 @@ class MailWhatsAppTemplate(models.Model):
256
123
  }
257
124
 
258
125
  def _prepare_components_to_export(self):
259
- body_component = {"type": "BODY", "text": self.body}
260
- body_params = self.variable_ids.filtered(lambda line: line.line_type == "body")
261
- if body_params:
262
- body_component["example"] = {
263
- "body_text": [body_params.mapped("sample_value")]
264
- }
265
- components = [body_component]
126
+ components = [{"type": "BODY", "text": self.body}]
266
127
  if self.header:
267
- header_component = {"type": "HEADER", "format": "TEXT", "text": self.header}
268
- header_params = self.variable_ids.filtered(
269
- lambda line: line.line_type == "header"
270
- )
271
- if header_params:
272
- header_component["example"] = {
273
- "header_text": header_params.mapped("sample_value")
128
+ components.append(
129
+ {
130
+ "type": "HEADER",
131
+ "format": "text",
132
+ "text": self.header,
274
133
  }
275
- components.append(header_component)
134
+ )
276
135
  if self.footer:
277
- components.append({"type": "FOOTER", "text": self.footer})
278
- buttons = []
279
- for button in self.button_ids:
280
- button_data = {"type": button.button_type.upper(), "text": button.name}
281
- if button.button_type == "url":
282
- button_data["url"] = button.website_url
283
- if button.url_type == "dynamic":
284
- button_data["url"] += "{{1}}"
285
- button_data["example"] = button.variable_ids[0].sample_value
286
- elif button.button_type == "phone_number":
287
- button_data["phone_number"] = button.call_number
288
- buttons.append(button_data)
289
- if buttons:
290
- components.append({"type": "BUTTONS", "buttons": buttons})
291
- # TODO: add more components(location, etc)
136
+ components.append(
137
+ {
138
+ "type": "FOOTER",
139
+ "text": self.footer,
140
+ }
141
+ )
142
+ # TODO: add more components(buttons, location, etc)
292
143
  return components
293
144
 
294
145
  def button_sync_template(self):
@@ -301,7 +152,7 @@ class MailWhatsAppTemplate(models.Model):
301
152
  try:
302
153
  response = requests.get(
303
154
  template_url,
304
- headers={"Authorization": "Bearer %s" % gateway.token},
155
+ headers={"Authorization": f"Bearer {gateway.token}"},
305
156
  timeout=10,
306
157
  )
307
158
  response.raise_for_status()
@@ -334,382 +185,7 @@ class MailWhatsAppTemplate(models.Model):
334
185
  vals["body"] = component["text"]
335
186
  elif component["type"] == "FOOTER":
336
187
  vals["footer"] = component["text"]
337
- elif component["type"] == "BUTTONS":
338
- for index, button in enumerate(component["buttons"]):
339
- if button["type"] in ("URL", "PHONE_NUMBER", "QUICK_REPLY"):
340
- button_vals = {
341
- "sequence": index,
342
- "name": button["text"],
343
- "button_type": button["type"].lower(),
344
- "call_number": button.get("phone_number"),
345
- "website_url": button.get("url"),
346
- }
347
- vals.setdefault("button_ids", [])
348
- button = self.button_ids.filtered(
349
- lambda btn, button=button: btn.name == button["text"]
350
- )
351
- if button:
352
- vals["button_ids"].append(
353
- Command.update(button.id, button_vals)
354
- )
355
- else:
356
- vals["button_ids"].append(Command.create(button_vals))
357
188
  else:
358
189
  is_supported = False
359
190
  vals["is_supported"] = is_supported
360
191
  return vals
361
-
362
- def _prepare_header_component(self, variable_ids_value):
363
- header = []
364
- if self.header and variable_ids_value.get("header-{{1}}"):
365
- value = variable_ids_value.get("header-{{1}}") or (self.header or {}) or ""
366
- header = {"type": "header", "parameters": [{"type": "text", "text": value}]}
367
- return header
368
-
369
- def _prepare_body_components(self, variable_ids_value):
370
- if not self.variable_ids:
371
- return None
372
- parameters = []
373
- for body_val in self.variable_ids.filtered(
374
- lambda line: line.line_type == "body"
375
- ):
376
- parameters.append(
377
- {
378
- "type": "text",
379
- "text": variable_ids_value.get(
380
- f"{body_val.line_type}-{body_val.name}"
381
- )
382
- or " ",
383
- }
384
- )
385
- return {"type": "body", "parameters": parameters}
386
-
387
- def _prepare_button_components(self, variable_ids_value):
388
- components = []
389
- dynamic_buttons = self.button_ids.filtered(
390
- lambda line: line.url_type == "dynamic"
391
- )
392
- index = {button: i for i, button in enumerate(self.button_ids)}
393
- for button in dynamic_buttons:
394
- dynamic_url = button.website_url
395
- value = variable_ids_value.get(f"button-{button.name}") or " "
396
- value = value.replace(dynamic_url, "").lstrip("/")
397
- components.append(
398
- {
399
- "type": "button",
400
- "sub_type": "url",
401
- "index": index.get(button),
402
- "parameters": [{"type": "text", "text": value}],
403
- }
404
- )
405
- return components
406
-
407
- def prepare_value_to_send(self):
408
- self.ensure_one()
409
- model_name = self.model_id.model
410
- rec_id = self.env.context.get("default_res_id")
411
- if rec_id is None:
412
- rec_ids = self.env.context.get("res_id")
413
- if rec_ids:
414
- rec_id = rec_ids
415
- else:
416
- rec_id = None
417
- if model_name and rec_id:
418
- record = self.env[model_name].browse(int(rec_id))
419
- components = []
420
- variable_ids_value = self.variable_ids._get_variables_value(record)
421
- # generate components
422
- header = self._prepare_header_component(variable_ids_value=variable_ids_value)
423
- body = self._prepare_body_components(variable_ids_value=variable_ids_value)
424
- buttons = self._prepare_button_components(variable_ids_value=variable_ids_value)
425
- if header:
426
- components.append(header)
427
- if body:
428
- components.append(body)
429
- components.extend(buttons)
430
- return components
431
-
432
- def render_body_message(self):
433
- self.ensure_one()
434
- model_name = self.model_id.model
435
- rec_id = self.env.context.get("default_res_id")
436
- if rec_id is None:
437
- rec_ids = self.env.context.get("default_res_ids")
438
- if isinstance(rec_ids, list) and rec_ids:
439
- rec_id = rec_ids[0]
440
- else:
441
- rec_id = None
442
- if model_name and rec_id:
443
- record = self.env[model_name].browse(int(rec_id))
444
- header = ""
445
- if self.header:
446
- header = self.header
447
- header_vars = self.variable_ids.filtered(lambda v: v.line_type == "header")
448
- for i, var in enumerate(header_vars, start=1):
449
- placeholder = f"{{{{{i}}}}}"
450
- value = var._get_variables_value(record).get(
451
- f"header-{placeholder}", ""
452
- )
453
- header = header.replace(placeholder, str(value))
454
- body = self.body or ""
455
- body_vars = self.variable_ids.filtered(lambda v: v.line_type == "body")
456
- for i, var in enumerate(body_vars, start=1):
457
- placeholder = f"{{{{{i}}}}}"
458
- value = var._get_variables_value(record).get(f"body-{placeholder}", "")
459
- body = body.replace(placeholder, str(value))
460
- message = f"*{header}*\n\n{body}" if header else body
461
- return message
462
-
463
-
464
- class MailWhatsAppTemplateVariable(models.Model):
465
- _name = "mail.whatsapp.template.variable"
466
- _description = "WhatsApp Template Variable"
467
- _order = "line_type desc, name, id"
468
-
469
- name = fields.Char(string="Placeholder", required=True)
470
- template_id = fields.Many2one(
471
- comodel_name="mail.whatsapp.template", required=True, ondelete="cascade"
472
- )
473
- model = fields.Char(string="Model Name", related="template_id.model")
474
- line_type = fields.Selection(
475
- [("header", "Header"), ("body", "Body"), ("button", "Button")], required=True
476
- )
477
- field_name = fields.Char(string="Field")
478
- sample_value = fields.Char(default="Sample Value", required=True)
479
- button_id = fields.Many2one("mail.whatsapp.template.button", ondelete="cascade")
480
-
481
- _sql_constraints = [
482
- (
483
- "name_type_template_unique",
484
- "UNIQUE(name, line_type, template_id,button_id)",
485
- "Variable names must be unique by template",
486
- ),
487
- ]
488
-
489
- @api.constrains("field_name")
490
- def _check_field_name(self):
491
- failing = self.browse()
492
- missing = self.filtered(lambda variable: not variable.field_name)
493
- if missing:
494
- raise ValidationError(
495
- _(
496
- "Field template variables %(variables)s "
497
- "must be associated with a field.",
498
- variables=", ".join(missing.mapped("name")),
499
- )
500
- )
501
- for variable in self:
502
- model = self.env[variable.model]
503
- if not model.check_access_rights("read", raise_exception=False):
504
- model_description = (
505
- self.env["ir.model"]._get(variable.model).display_name
506
- )
507
- raise ValidationError(
508
- _("You can not select field of %(model)s.", model=model_description)
509
- )
510
- try:
511
- variable._extract_value_from_field_path(model)
512
- except UserError:
513
- failing += variable
514
- if failing:
515
- model_description = (
516
- self.env["ir.model"]._get(failing.mapped("model")[0]).display_name
517
- )
518
- raise ValidationError(
519
- _(
520
- "Variables %(field_names)s do not seem to be valid field path "
521
- "for model %(model_name)s.",
522
- field_names=", ".join(failing.mapped("field_name")),
523
- model_name=model_description,
524
- )
525
- )
526
-
527
- @api.constrains("name")
528
- def _check_name(self):
529
- for variable in self:
530
- if not variable._extract_variable_index():
531
- raise ValidationError(
532
- _(
533
- "Template variable should be in format {{number}}. "
534
- "Cannot parse '%(placeholder)s'",
535
- placeholder=variable.name,
536
- )
537
- )
538
-
539
- @api.depends("line_type", "name")
540
- def _compute_display_name(self):
541
- type_names = dict(self._fields["line_type"]._description_selection(self.env))
542
- for variable in self:
543
- type_name = type_names[variable.line_type or "body"]
544
- variable.display_name = (
545
- type_name
546
- if variable.line_type == "header"
547
- else f"{type_name} - {variable.name}"
548
- )
549
-
550
- @api.onchange("model")
551
- def _onchange_model_id(self):
552
- self.field_name = False
553
-
554
- def _get_variables_value(self, record):
555
- value_by_name = {}
556
- for variable in self:
557
- value = variable._extract_value_from_field_path(record)
558
- value_str = value and str(value) or ""
559
- value_key = f"{variable.line_type}-{variable.name}"
560
- value_by_name[value_key] = value_str
561
- return value_by_name
562
-
563
- def _extract_variable_index(self):
564
- """Extract variable index, located between '{{}}' markers."""
565
- self.ensure_one()
566
- try:
567
- return int(self.name.replace("{{", "").replace("}}", ""))
568
- except ValueError:
569
- return None
570
-
571
- def _extract_value_from_field_path(self, record):
572
- field_path = self.field_name
573
- if not field_path:
574
- return ""
575
- try:
576
- field_value = record.mapped(field_path)
577
- except Exception as err:
578
- raise UserError(
579
- _(
580
- "We were not able to fetch value of field: %(field)s",
581
- field=field_path,
582
- )
583
- ) from err
584
- if isinstance(field_value, models.Model):
585
- return " ".join((value.display_name or "") for value in field_value)
586
- # find last field / last model when having chained fields
587
- # e.g. 'partner_id.country_id.state' -> ['partner_id.country_id', 'state']
588
- field_path_models = field_path.rsplit(".", 1)
589
- if len(field_path_models) > 1:
590
- last_model_path, last_fname = field_path_models
591
- last_model = record.mapped(last_model_path)
592
- else:
593
- last_model, last_fname = record, field_path
594
- last_field = last_model._fields[last_fname]
595
- # return value instead of the key
596
- if last_field.type == "selection":
597
- return " ".join(
598
- last_field.convert_to_export(value, last_model) for value in field_value
599
- )
600
- return " ".join(
601
- str(value if value is not False and value is not None else "")
602
- for value in field_value
603
- )
604
-
605
-
606
- class MailWhatsAppTemplateButton(models.Model):
607
- _name = "mail.whatsapp.template.button"
608
- _description = "WhatsApp Template Button"
609
- _order = "sequence,id"
610
-
611
- sequence = fields.Integer()
612
- name = fields.Char(string="Button Text", size=25)
613
- template_id = fields.Many2one(
614
- comodel_name="mail.whatsapp.template", required=True, ondelete="cascade"
615
- )
616
- button_type = fields.Selection(
617
- [
618
- ("quick_reply", "Quick Reply"),
619
- ("url", "Visit Website"),
620
- ("phone_number", "Call Number"),
621
- ],
622
- string="Type",
623
- required=True,
624
- default="quick_reply",
625
- )
626
- url_type = fields.Selection(
627
- [("static", "Static"), ("dynamic", "Dynamic")],
628
- default="static",
629
- )
630
- website_url = fields.Char()
631
- call_number = fields.Char()
632
- variable_ids = fields.One2many(
633
- "mail.whatsapp.template.variable",
634
- "button_id",
635
- compute="_compute_variable_ids",
636
- precompute=True,
637
- store=True,
638
- copy=True,
639
- )
640
-
641
- _sql_constraints = [
642
- (
643
- "unique_name_per_template",
644
- "UNIQUE(name, template_id)",
645
- "Button name must be unique by template",
646
- )
647
- ]
648
-
649
- @api.constrains("button_type", "url_type", "website_url")
650
- def _validate_website_url(self):
651
- for button in self.filtered(lambda button: button.button_type == "url"):
652
- parsed_url = urlparse(button.website_url)
653
- if not (parsed_url.scheme in {"http", "https"} and parsed_url.netloc):
654
- raise ValidationError(
655
- _(
656
- "Please enter a valid URL in the format 'https://www.example.com'."
657
- )
658
- )
659
-
660
- @api.constrains("call_number")
661
- def _validate_call_number(self):
662
- for button in self:
663
- if button.button_type == "phone_number":
664
- phone_validation.phone_format(button.call_number, False, False)
665
-
666
- @api.depends("button_type", "url_type", "website_url", "name")
667
- def _compute_variable_ids(self):
668
- dynamic_urls = self.filtered(
669
- lambda button: button.button_type == "url" and button.url_type == "dynamic"
670
- )
671
- to_clear = self - dynamic_urls
672
- for button in dynamic_urls:
673
- if button.variable_ids:
674
- button.variable_ids = [
675
- (
676
- 1,
677
- button.variable_ids[0].id,
678
- {
679
- "sample_value": button.website_url,
680
- "line_type": "button",
681
- "name": button.name,
682
- "button_id": button.id,
683
- "template_id": button.template_id.id,
684
- },
685
- ),
686
- ]
687
- else:
688
- button.variable_ids = [
689
- (
690
- 0,
691
- 0,
692
- {
693
- "sample_value": button.website_url,
694
- "line_type": "button",
695
- "name": button.name,
696
- "button_id": button.id,
697
- "template_id": button.template_id.id,
698
- },
699
- ),
700
- ]
701
- if to_clear:
702
- to_clear.variable_ids = [(5, 0)]
703
-
704
- def check_variable_ids(self):
705
- for button in self:
706
- if len(button.variable_ids) > 1:
707
- raise ValidationError(_("Buttons may only contain one placeholder."))
708
- if button.variable_ids and button.url_type != "dynamic":
709
- raise ValidationError(_("Only dynamic urls may have a placeholder."))
710
- elif button.url_type == "dynamic" and not button.variable_ids:
711
- raise ValidationError(_("All dynamic urls must have a placeholder."))
712
- if button.variable_ids.name != "{{1}}":
713
- raise ValidationError(
714
- _("The placeholder for a button can only be {{1}}.")
715
- )
@@ -2,7 +2,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2
2
  access_whatsapp_composer,access.whatsapp.composer,model_whatsapp_composer,base.group_user,1,1,1,0
3
3
  access_mail_whatsapp_template_group_system,mail_whatsapp_template_group_system,model_mail_whatsapp_template,base.group_system,1,1,1,1
4
4
  access_mail_whatsapp_template_group_user,mail_whatsapp_template_group_user,model_mail_whatsapp_template,base.group_user,1,0,0,0
5
- access_mail_whatsapp_template_button_group_system,access_mail_whatsapp_template_button_group_system,model_mail_whatsapp_template_button,base.group_system,1,1,1,1
6
- access_mail_whatsapp_template_button_group_user,access_mail_whatsapp_template_button_group_user,model_mail_whatsapp_template_button,base.group_user,1,0,0,0
7
- access_mail_whatsapp_template_variable_group_system,access_mail_whatsapp_template_variable_group_system,model_mail_whatsapp_template_variable,base.group_system,1,1,1,1
8
- access_mail_whatsapp_template_variable_group_user,access_mail_whatsapp_template_variable_group_user,model_mail_whatsapp_template_variable,base.group_user,1,0,0,0
@@ -372,9 +372,9 @@ ul.auto-toc {
372
372
  !! This file is generated by oca-gen-addon-readme !!
373
373
  !! changes will be overwritten. !!
374
374
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
375
- !! source digest: sha256:461c4bb588dcbc2d98136e081b3781fcf72074c29ab4f11c34e345d1defccfcc
375
+ !! source digest: sha256:0c5051305d7d04fb4c093d04523e165482783b281bc5c547fac9cf16c15c6ee3
376
376
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
377
- <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/social/tree/17.0/mail_gateway_whatsapp"><img alt="OCA/social" src="https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/social-17-0/social-17-0-mail_gateway_whatsapp"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/social&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
377
+ <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/social/tree/18.0/mail_gateway_whatsapp"><img alt="OCA/social" src="https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/social-18-0/social-18-0-mail_gateway_whatsapp"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/social&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
378
378
  <p>This module allows to respond whatsapp chats.</p>
379
379
  <p>This way, a group of users can respond customers or any other set of
380
380
  partners in an integrated way.</p>
@@ -463,7 +463,7 @@ as validation Key</li>
463
463
  <p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/social/issues">GitHub Issues</a>.
464
464
  In case of trouble, please check there if your issue has already been reported.
465
465
  If you spotted it first, help us to smash it by providing a detailed and welcomed
466
- <a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_gateway_whatsapp%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
466
+ <a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_gateway_whatsapp%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
467
467
  <p>Do not contact contributors directly about support or help with technical issues.</p>
468
468
  </div>
469
469
  <div class="section" id="credits">
@@ -505,7 +505,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
505
505
  <p>OCA, or the Odoo Community Association, is a nonprofit organization whose
506
506
  mission is to support the collaborative development of Odoo features and
507
507
  promote its widespread use.</p>
508
- <p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/17.0/mail_gateway_whatsapp">OCA/social</a> project on GitHub.</p>
508
+ <p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/18.0/mail_gateway_whatsapp">OCA/social</a> project on GitHub.</p>
509
509
  <p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
510
510
  </div>
511
511
  </div>
@@ -1,4 +1,3 @@
1
- /* @odoo-module */
2
1
  import {Message} from "@mail/core/common/message";
3
2
  import {patch} from "@web/core/utils/patch";
4
3
  import {url} from "@web/core/utils/urls";