mirror of
https://github.com/bringout/oca-ocb-test.git
synced 2026-04-23 06:42:01 +02:00
Initial commit: Test packages
This commit is contained in:
commit
080accd21c
338 changed files with 32413 additions and 0 deletions
59
odoo-bringout-oca-ocb-test_mail_full/README.md
Normal file
59
odoo-bringout-oca-ocb-test_mail_full/README.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Mail Tests (Full)
|
||||
|
||||
This module contains tests related to various mail features
|
||||
and mail-related sub modules. Those tests are present in a separate module as it
|
||||
contains models used only to perform tests independently to functional aspects of
|
||||
real applications.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-ocb-test_mail_full
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- mail
|
||||
- mail_bot
|
||||
- portal
|
||||
- rating
|
||||
- mass_mailing
|
||||
- mass_mailing_sms
|
||||
- phone_validation
|
||||
- sms
|
||||
- test_mail
|
||||
- test_mail_sms
|
||||
- test_mass_mailing
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Mail Tests (Full)
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_mail_full`.
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
32
odoo-bringout-oca-ocb-test_mail_full/doc/ARCHITECTURE.md
Normal file
32
odoo-bringout-oca-ocb-test_mail_full/doc/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U[Users] -->|HTTP| V[Views and QWeb Templates]
|
||||
V --> C[Controllers]
|
||||
V --> W[Wizards – Transient Models]
|
||||
C --> M[Models and ORM]
|
||||
W --> M
|
||||
M --> R[Reports]
|
||||
DX[Data XML] --> M
|
||||
S[Security – ACLs and Groups] -. enforces .-> M
|
||||
|
||||
subgraph Test_mail_full Module - test_mail_full
|
||||
direction LR
|
||||
M:::layer
|
||||
W:::layer
|
||||
C:::layer
|
||||
V:::layer
|
||||
R:::layer
|
||||
S:::layer
|
||||
DX:::layer
|
||||
end
|
||||
|
||||
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
|
||||
```
|
||||
|
||||
Notes
|
||||
- Views include tree/form/kanban templates and report templates.
|
||||
- Controllers provide website/portal routes when present.
|
||||
- Wizards are UI flows implemented with `models.TransientModel`.
|
||||
- Data XML loads data/demo records; Security defines groups and access.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for test_mail_full. Configure related models, access rights, and options as needed.
|
||||
17
odoo-bringout-oca-ocb-test_mail_full/doc/CONTROLLERS.md
Normal file
17
odoo-bringout-oca-ocb-test_mail_full/doc/CONTROLLERS.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Controllers
|
||||
|
||||
HTTP routes provided by this module.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as User/Client
|
||||
participant C as Module Controllers
|
||||
participant O as ORM/Views
|
||||
|
||||
U->>C: HTTP GET/POST (routes)
|
||||
C->>O: ORM operations, render templates
|
||||
O-->>U: HTML/JSON/PDF
|
||||
```
|
||||
|
||||
Notes
|
||||
- See files in controllers/ for route definitions.
|
||||
15
odoo-bringout-oca-ocb-test_mail_full/doc/DEPENDENCIES.md
Normal file
15
odoo-bringout-oca-ocb-test_mail_full/doc/DEPENDENCIES.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
- [mail](../../odoo-bringout-oca-ocb-mail)
|
||||
- [mail_bot](../../odoo-bringout-oca-ocb-mail_bot)
|
||||
- [portal](../../odoo-bringout-oca-ocb-portal)
|
||||
- [rating](../../odoo-bringout-oca-ocb-rating)
|
||||
- [mass_mailing](../../odoo-bringout-oca-ocb-mass_mailing)
|
||||
- [mass_mailing_sms](../../odoo-bringout-oca-ocb-mass_mailing_sms)
|
||||
- [phone_validation](../../odoo-bringout-oca-ocb-phone_validation)
|
||||
- [sms](../../odoo-bringout-oca-ocb-sms)
|
||||
- [test_mail](../../odoo-bringout-oca-ocb-test_mail)
|
||||
- [test_mail_sms](../../odoo-bringout-oca-ocb-test_mail_sms)
|
||||
- [test_mass_mailing](../../odoo-bringout-oca-ocb-test_mass_mailing)
|
||||
4
odoo-bringout-oca-ocb-test_mail_full/doc/FAQ.md
Normal file
4
odoo-bringout-oca-ocb-test_mail_full/doc/FAQ.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# FAQ
|
||||
|
||||
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
|
||||
- Q: How to enable? A: Start server with --addon test_mail_full or install in UI.
|
||||
7
odoo-bringout-oca-ocb-test_mail_full/doc/INSTALL.md
Normal file
7
odoo-bringout-oca-ocb-test_mail_full/doc/INSTALL.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-ocb-test_mail_full"
|
||||
# or
|
||||
uv pip install odoo-bringout-oca-ocb-test_mail_full"
|
||||
```
|
||||
16
odoo-bringout-oca-ocb-test_mail_full/doc/MODELS.md
Normal file
16
odoo-bringout-oca-ocb-test_mail_full/doc/MODELS.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Models
|
||||
|
||||
Detected core models and extensions in test_mail_full.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class mail_test_portal
|
||||
class mail_test_portal_no_partner
|
||||
class mail_test_portal_public_access_action
|
||||
class mail_test_rating
|
||||
class mail_test_portal
|
||||
```
|
||||
|
||||
Notes
|
||||
- Classes show model technical names; fields omitted for brevity.
|
||||
- Items listed under _inherit are extensions of existing models.
|
||||
6
odoo-bringout-oca-ocb-test_mail_full/doc/OVERVIEW.md
Normal file
6
odoo-bringout-oca-ocb-test_mail_full/doc/OVERVIEW.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Overview
|
||||
|
||||
Packaged Odoo addon: test_mail_full. Provides features documented in upstream Odoo 16 under this addon.
|
||||
|
||||
- Source: OCA/OCB 16.0, addon test_mail_full
|
||||
- License: LGPL-3
|
||||
3
odoo-bringout-oca-ocb-test_mail_full/doc/REPORTS.md
Normal file
3
odoo-bringout-oca-ocb-test_mail_full/doc/REPORTS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
42
odoo-bringout-oca-ocb-test_mail_full/doc/SECURITY.md
Normal file
42
odoo-bringout-oca-ocb-test_mail_full/doc/SECURITY.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Security
|
||||
|
||||
Access control and security definitions in test_mail_full.
|
||||
|
||||
## Access Control Lists (ACLs)
|
||||
|
||||
Model access permissions defined in:
|
||||
- **[ir.model.access.csv](../test_mail_full/security/ir.model.access.csv)**
|
||||
- 9 model access rules
|
||||
|
||||
## Record Rules
|
||||
|
||||
Row-level security rules defined in:
|
||||
- **[ir_rule_data.xml](../test_mail_full/security/ir_rule_data.xml)**
|
||||
|
||||
## Security Groups & Configuration
|
||||
|
||||
Security groups and permissions defined in:
|
||||
- **[ir_rule_data.xml](../test_mail_full/security/ir_rule_data.xml)**
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Security Layers"
|
||||
A[Users] --> B[Groups]
|
||||
B --> C[Access Control Lists]
|
||||
C --> D[Models]
|
||||
B --> E[Record Rules]
|
||||
E --> F[Individual Records]
|
||||
end
|
||||
```
|
||||
|
||||
Security files overview:
|
||||
- **[ir.model.access.csv](../test_mail_full/security/ir.model.access.csv)**
|
||||
- Model access permissions (CRUD rights)
|
||||
- **[ir_rule_data.xml](../test_mail_full/security/ir_rule_data.xml)**
|
||||
- Security groups, categories, and XML-based rules
|
||||
|
||||
Notes
|
||||
- Access Control Lists define which groups can access which models
|
||||
- Record Rules provide row-level security (filter records by user/group)
|
||||
- Security groups organize users and define permission sets
|
||||
- All security is enforced at the ORM level by Odoo
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Troubleshooting
|
||||
|
||||
- Ensure Python and Odoo environment matches repo guidance.
|
||||
- Check database connectivity and logs if startup fails.
|
||||
- Validate that dependent addons listed in DEPENDENCIES.md are installed.
|
||||
7
odoo-bringout-oca-ocb-test_mail_full/doc/USAGE.md
Normal file
7
odoo-bringout-oca-ocb-test_mail_full/doc/USAGE.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Usage
|
||||
|
||||
Start Odoo including this addon (from repo root):
|
||||
|
||||
```bash
|
||||
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon test_mail_full
|
||||
```
|
||||
3
odoo-bringout-oca-ocb-test_mail_full/doc/WIZARDS.md
Normal file
3
odoo-bringout-oca-ocb-test_mail_full/doc/WIZARDS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Wizards
|
||||
|
||||
This module does not include UI wizards.
|
||||
52
odoo-bringout-oca-ocb-test_mail_full/pyproject.toml
Normal file
52
odoo-bringout-oca-ocb-test_mail_full/pyproject.toml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_mail_full"
|
||||
version = "16.0.0"
|
||||
description = "Mail Tests (Full) - Mail Tests: performances and tests specific to mail with all sub-modules"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-mail>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mail_bot>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-portal>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-rating>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mass_mailing>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mass_mailing_sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-phone_validation>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mail>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mail_sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mass_mailing>=16.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/bringout/0"
|
||||
repository = "https://github.com/bringout/0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["test_mail_full"]
|
||||
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import tests
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
{
|
||||
'name': 'Mail Tests (Full)',
|
||||
'version': '1.0',
|
||||
'category': 'Hidden',
|
||||
'sequence': 9876,
|
||||
'summary': 'Mail Tests: performances and tests specific to mail with all sub-modules',
|
||||
'description': """This module contains tests related to various mail features
|
||||
and mail-related sub modules. Those tests are present in a separate module as it
|
||||
contains models used only to perform tests independently to functional aspects of
|
||||
real applications. """,
|
||||
'depends': [
|
||||
'mail',
|
||||
'mail_bot',
|
||||
'portal',
|
||||
'rating',
|
||||
# 'snailmail',
|
||||
'mass_mailing',
|
||||
'mass_mailing_sms', # adds portal
|
||||
'phone_validation',
|
||||
'sms',
|
||||
'test_mail',
|
||||
'test_mail_sms',
|
||||
'test_mass_mailing',
|
||||
],
|
||||
'data': [
|
||||
'data/mail_message_subtype_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/ir_rule_data.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.qunit_suite_tests': [
|
||||
'test_mail_full/static/tests/qunit_suite_tests/*.js',
|
||||
],
|
||||
'web.tests_assets': [
|
||||
'test_mail_full/static/tests/helpers/*.js',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import portal
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class PortalTest(http.Controller):
|
||||
"""Implements some test portal routes (ex.: for viewing a record)."""
|
||||
|
||||
@http.route('/my/test_portal/<int:res_id>', type='http', auth='public', methods=['GET'])
|
||||
def test_portal_record_view(self, res_id, access_token=None, **kwargs):
|
||||
return request.make_response(f'Record view of test_portal {res_id} ({access_token}, {kwargs})')
|
||||
|
||||
@http.route('/test_portal/public_type/<int:res_id>', type='http', auth='public', methods=['GET'])
|
||||
def test_public_record_view(self, res_id):
|
||||
return request.make_response(f'Testing public controller for {res_id}')
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mt_mail_test_rating_rating_done" model="mail.message.subtype">
|
||||
<field name="name">Rating Done</field>
|
||||
<field name="description">Rating Done</field>
|
||||
<field name="res_model">mail.test.rating</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="internal" eval="False"/>
|
||||
</record>
|
||||
</odoo>
|
||||
291
odoo-bringout-oca-ocb-test_mail_full/test_mail_full/i18n/bs.po
Normal file
291
odoo-bringout-oca-ocb-test_mail_full/test_mail_full/i18n/bs.po
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * test_mail_full
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~12.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-09 10:49+0000\n"
|
||||
"PO-Revision-Date: 2019-09-09 10:49+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_needaction
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_needaction
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_needaction
|
||||
msgid "Action Needed"
|
||||
msgstr "Potrebna akcija"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_attachment_count
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_attachment_count
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_attachment_count
|
||||
msgid "Attachment Count"
|
||||
msgstr "Broj priloga"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model,name:test_mail_full.model_mail_test_sms
|
||||
#: model:ir.model,name:test_mail_full.model_mail_test_sms_bl
|
||||
msgid "Chatter Model for SMS Gateway"
|
||||
msgstr "Chatter model za SMS pristupnik"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model,name:test_mail_full.model_mail_test_sms_partner
|
||||
msgid "Chatter Model for SMS Gateway (Partner only)"
|
||||
msgstr "Chatter model za SMS pristupnik (samo partner)"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__create_uid
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__create_uid
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Kreirao"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__create_date
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__create_date
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__create_date
|
||||
msgid "Created on"
|
||||
msgstr "Kreirano"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__customer_id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__customer_id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__partner_id
|
||||
msgid "Customer"
|
||||
msgstr "Kupac"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__display_name
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__display_name
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Prikazani naziv"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__email_from
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__email_from
|
||||
msgid "Email From"
|
||||
msgstr "Email od"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__phone_sanitized
|
||||
msgid ""
|
||||
"Field used to store sanitized phone number. Helps speeding up searches and "
|
||||
"comparisons."
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_follower_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_follower_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_follower_ids
|
||||
msgid "Followers"
|
||||
msgstr "Pratioci"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_channel_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_channel_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_channel_ids
|
||||
msgid "Followers (Channels)"
|
||||
msgstr "Pratioci (Kanali)"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_partner_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_partner_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_partner_ids
|
||||
msgid "Followers (Partners)"
|
||||
msgstr "Pratioci (Partneri)"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_needaction
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_unread
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_needaction
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_unread
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_needaction
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_unread
|
||||
msgid "If checked, new messages require your attention."
|
||||
msgstr "Ako je zakačeno, nove poruke će zahtjevati vašu pažnju"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_has_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_has_sms_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_has_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_has_sms_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_has_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_has_sms_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr "Ako je označeno neke poruke mogu imati grešku u dostavi."
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__phone_blacklisted
|
||||
msgid ""
|
||||
"If the email address is on the blacklist, the contact won't receive mass "
|
||||
"mailing anymore, from any list"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_is_follower
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_is_follower
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_is_follower
|
||||
msgid "Is Follower"
|
||||
msgstr "Pratilac"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms____last_update
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl____last_update
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Zadnje mijenjano"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__write_uid
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__write_uid
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Zadnji ažurirao"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__write_date
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__write_date
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Zadnje ažurirano"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_main_attachment_id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_main_attachment_id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_main_attachment_id
|
||||
msgid "Main Attachment"
|
||||
msgstr "Glavna zakačka"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_error
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_has_error
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_has_error
|
||||
msgid "Message Delivery error"
|
||||
msgstr "Greška pri isporuci poruke"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_ids
|
||||
msgid "Messages"
|
||||
msgstr "Poruke"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__mobile_nbr
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__mobile_nbr
|
||||
msgid "Mobile Nbr"
|
||||
msgstr "Broj mobilnog"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__name
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__name
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__name
|
||||
msgid "Name"
|
||||
msgstr "Naziv:"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_needaction_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_needaction_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_needaction_counter
|
||||
msgid "Number of Actions"
|
||||
msgstr "Broj akcija"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_error_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_has_error_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_has_error_counter
|
||||
msgid "Number of errors"
|
||||
msgstr "Broj grešaka"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_needaction_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_needaction_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_needaction_counter
|
||||
msgid "Number of messages requiring action"
|
||||
msgstr "Broj poruka koje zahtijevaju aktivnost"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_has_error_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_has_error_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr "Broj poruka sa greškama pri isporuci"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_unread_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_unread_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_unread_counter
|
||||
msgid "Number of unread messages"
|
||||
msgstr "Broj nepročitanih poruka"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__phone_blacklisted
|
||||
msgid "Phone Blacklisted"
|
||||
msgstr "Telefon je stavljen na crnu listu"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__phone_nbr
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__phone_nbr
|
||||
msgid "Phone Nbr"
|
||||
msgstr "Broj telefona"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_sms_error
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_has_sms_error
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_has_sms_error
|
||||
msgid "SMS Delivery error"
|
||||
msgstr "Greška u slanju SMSa"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__phone_sanitized
|
||||
msgid "Sanitized Number"
|
||||
msgstr "Sanirani broj"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__subject
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__subject
|
||||
msgid "Subject"
|
||||
msgstr "Tema"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_unread
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_unread
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_unread
|
||||
msgid "Unread Messages"
|
||||
msgstr "Nepročitane poruke"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_unread_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_unread_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_unread_counter
|
||||
msgid "Unread Messages Counter"
|
||||
msgstr "Brojač nepročitanih poruka"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__website_message_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__website_message_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__website_message_ids
|
||||
msgid "Website Messages"
|
||||
msgstr "Poruke sa website-a"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__website_message_ids
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__website_message_ids
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__website_message_ids
|
||||
msgid "Website communication history"
|
||||
msgstr "Povijest komunikacije Web stranice"
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * test_mail_full
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~12.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-09 10:49+0000\n"
|
||||
"PO-Revision-Date: 2019-09-09 10:49+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_needaction
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_needaction
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_needaction
|
||||
msgid "Action Needed"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_attachment_count
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_attachment_count
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_attachment_count
|
||||
msgid "Attachment Count"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model,name:test_mail_full.model_mail_test_sms
|
||||
#: model:ir.model,name:test_mail_full.model_mail_test_sms_bl
|
||||
msgid "Chatter Model for SMS Gateway"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model,name:test_mail_full.model_mail_test_sms_partner
|
||||
msgid "Chatter Model for SMS Gateway (Partner only)"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__create_uid
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__create_uid
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__create_uid
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__create_date
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__create_date
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__create_date
|
||||
msgid "Created on"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__customer_id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__customer_id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__partner_id
|
||||
msgid "Customer"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__display_name
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__display_name
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__email_from
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__email_from
|
||||
msgid "Email From"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__phone_sanitized
|
||||
msgid ""
|
||||
"Field used to store sanitized phone number. Helps speeding up searches and "
|
||||
"comparisons."
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_follower_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_follower_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_follower_ids
|
||||
msgid "Followers"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_channel_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_channel_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_channel_ids
|
||||
msgid "Followers (Channels)"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_partner_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_partner_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_partner_ids
|
||||
msgid "Followers (Partners)"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_needaction
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_unread
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_needaction
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_unread
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_needaction
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_unread
|
||||
msgid "If checked, new messages require your attention."
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_has_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_has_sms_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_has_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_has_sms_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_has_error
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_has_sms_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__phone_blacklisted
|
||||
msgid ""
|
||||
"If the email address is on the blacklist, the contact won't receive mass "
|
||||
"mailing anymore, from any list"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_is_follower
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_is_follower
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_is_follower
|
||||
msgid "Is Follower"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms____last_update
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl____last_update
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__write_uid
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__write_uid
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__write_date
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__write_date
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_main_attachment_id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_main_attachment_id
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_main_attachment_id
|
||||
msgid "Main Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_error
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_has_error
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_has_error
|
||||
msgid "Message Delivery error"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_ids
|
||||
msgid "Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__mobile_nbr
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__mobile_nbr
|
||||
msgid "Mobile Nbr"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__name
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__name
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__name
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_needaction_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_needaction_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_needaction_counter
|
||||
msgid "Number of Actions"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_error_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_has_error_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_has_error_counter
|
||||
msgid "Number of errors"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_needaction_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_needaction_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_needaction_counter
|
||||
msgid "Number of messages requiring action"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_has_error_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_has_error_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_unread_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__message_unread_counter
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__message_unread_counter
|
||||
msgid "Number of unread messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__phone_blacklisted
|
||||
msgid "Phone Blacklisted"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__phone_nbr
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__phone_nbr
|
||||
msgid "Phone Nbr"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_sms_error
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_has_sms_error
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_has_sms_error
|
||||
msgid "SMS Delivery error"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__phone_sanitized
|
||||
msgid "Sanitized Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__subject
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__subject
|
||||
msgid "Subject"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_unread
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_unread
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_unread
|
||||
msgid "Unread Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_unread_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__message_unread_counter
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__message_unread_counter
|
||||
msgid "Unread Messages Counter"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__website_message_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__website_message_ids
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_partner__website_message_ids
|
||||
msgid "Website Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__website_message_ids
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__website_message_ids
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_partner__website_message_ids
|
||||
msgid "Website communication history"
|
||||
msgstr ""
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_mail_models_mail
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MailTestPortal(models.Model):
|
||||
""" A model intheriting from mail.thread with some fields used for portal
|
||||
sharing, like a partner, ..."""
|
||||
_description = 'Chatter Model for Portal'
|
||||
_name = 'mail.test.portal'
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'portal.mixin',
|
||||
]
|
||||
|
||||
name = fields.Char()
|
||||
partner_id = fields.Many2one('res.partner', 'Customer')
|
||||
user_id = fields.Many2one(comodel_name='res.users', string="Salesperson")
|
||||
|
||||
def _compute_access_url(self):
|
||||
super()._compute_access_url()
|
||||
for record in self.filtered('id'):
|
||||
record.access_url = '/my/test_portal/%s' % self.id
|
||||
|
||||
|
||||
class MailTestPortalNoPartner(models.Model):
|
||||
""" A model inheriting from portal, but without any partner field """
|
||||
_description = 'Chatter Model for Portal (no partner field)'
|
||||
_name = 'mail.test.portal.no.partner'
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'portal.mixin',
|
||||
]
|
||||
|
||||
name = fields.Char()
|
||||
|
||||
def _compute_access_url(self):
|
||||
self.access_url = False
|
||||
for record in self.filtered('id'):
|
||||
record.access_url = '/my/test_portal_no_partner/%s' % self.id
|
||||
|
||||
|
||||
class MailTestPortalPublicAccessAction(models.Model):
|
||||
""" Test 'public' target_type access action """
|
||||
_description = 'Portal Public Access Action'
|
||||
_name = 'mail.test.portal.public.access.action'
|
||||
_inherit = 'mail.test.portal'
|
||||
|
||||
def _compute_access_url(self):
|
||||
super()._compute_access_url()
|
||||
for record in self.filtered('id'):
|
||||
record.access_url = f'/test_portal/public_type/{record.id}'
|
||||
|
||||
def _get_access_action(self, access_uid=None, force_website=False):
|
||||
# Test 'public' target type for portal / public people
|
||||
if self.env.user.share or force_website:
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.access_url,
|
||||
'target': 'self',
|
||||
'target_type': 'public',
|
||||
'res_id': self.id,
|
||||
}
|
||||
return super()._get_access_action(access_uid=access_uid, force_website=force_website)
|
||||
|
||||
|
||||
class MailTestRating(models.Model):
|
||||
""" A model inheriting from mail.thread with some fields used for SMS
|
||||
gateway, like a partner, a specific mobile phone, ... """
|
||||
_description = 'Rating Model (ticket-like)'
|
||||
_name = 'mail.test.rating'
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'mail.activity.mixin',
|
||||
'rating.mixin',
|
||||
'portal.mixin',
|
||||
]
|
||||
_mailing_enabled = True
|
||||
_order = 'name asc, id asc'
|
||||
|
||||
name = fields.Char()
|
||||
subject = fields.Char()
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
email_from = fields.Char(compute='_compute_email_from', precompute=True, readonly=False, store=True)
|
||||
mobile_nbr = fields.Char(compute='_compute_mobile_nbr', precompute=True, readonly=False, store=True)
|
||||
phone_nbr = fields.Char(compute='_compute_phone_nbr', precompute=True, readonly=False, store=True)
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=1)
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_email_from(self):
|
||||
for rating in self:
|
||||
if rating.customer_id.email_normalized:
|
||||
rating.email_from = rating.customer_id.email_normalized
|
||||
elif not rating.email_from:
|
||||
rating.email_from = False
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_mobile_nbr(self):
|
||||
for rating in self:
|
||||
if rating.customer_id.mobile:
|
||||
rating.mobile_nbr = rating.customer_id.mobile
|
||||
elif not rating.mobile_nbr:
|
||||
rating.mobile_nbr = False
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_phone_nbr(self):
|
||||
for rating in self:
|
||||
if rating.customer_id.phone:
|
||||
rating.phone_nbr = rating.customer_id.phone
|
||||
elif not rating.phone_nbr:
|
||||
rating.phone_nbr = False
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
return ['customer_id']
|
||||
|
||||
def _rating_apply_get_default_subtype_id(self):
|
||||
return self.env['ir.model.data']._xmlid_to_res_id("test_mail_full.mt_mail_test_rating_rating_done")
|
||||
|
||||
def _rating_get_partner(self):
|
||||
return self.customer_id
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mail_test_portal_all,mail.test.portal.all,model_mail_test_portal,,0,0,0,0
|
||||
access_mail_test_portal_user,mail.test.portal.user,model_mail_test_portal,base.group_user,1,1,1,1
|
||||
access_mail_test_portal_no_partner_all,mail.test.portal.no.partner.all,model_mail_test_portal_no_partner,,1,0,0,0
|
||||
access_mail_test_portal_no_partner_user,mail.test.portal.no.partner.user,model_mail_test_portal_no_partner,base.group_user,1,1,1,1
|
||||
access_mail_test_portal_public_access_action_portal,mail.test.portal.public.access.action.portal,model_mail_test_portal_public_access_action,base.group_portal,1,0,0,0
|
||||
access_mail_test_portal_public_access_action_user,mail.test.portal.public.access.action.user,model_mail_test_portal_public_access_action,base.group_user,1,1,1,1
|
||||
access_mail_test_rating_all,mail.test.rating.all,model_mail_test_rating,,0,0,0,0
|
||||
access_mail_test_rating_portal,mail.test.rating.portal,model_mail_test_rating,base.group_portal,1,0,0,0
|
||||
access_mail_test_rating_user,mail.test.rating.user,model_mail_test_rating,base.group_user,1,1,1,1
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="mail_test_rating_rule_mc" model="ir.rule">
|
||||
<field name="name">TestRating: Multi Company</field>
|
||||
<field name="model_id" ref="test_mail_full.model_mail_test_rating"/>
|
||||
<field eval="True" name="global"/>
|
||||
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
|
||||
</record>
|
||||
<record id="mail_test_rating_rule_portal" model="ir.rule">
|
||||
<field name="name">TestRating: Portal should follow</field>
|
||||
<field name="model_id" ref="test_mail_full.model_mail_test_rating"/>
|
||||
<field name="domain_force">[('message_partner_ids', 'in', [user.partner_id.id])]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
addModelNamesToFetch(['mail.test.rating']);
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { afterNextRender, start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('test_mail_full', {}, function () {
|
||||
QUnit.module('channel_preview_view_tests.js');
|
||||
|
||||
QUnit.test('rating value displayed on the thread preview', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({});
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({});
|
||||
const mailMessageId1 = pyEnv['mail.message'].create([
|
||||
{ author_id: resPartnerId1, model: 'mail.channel', res_id: mailChannelId1 },
|
||||
]);
|
||||
pyEnv['rating.rating'].create({
|
||||
consumed: true,
|
||||
message_id: mailMessageId1,
|
||||
partner_id: resPartnerId1,
|
||||
rating_image_url: "/rating/static/src/img/rating_5.png",
|
||||
rating_text: "top",
|
||||
});
|
||||
const { afterEvent, messaging } = await start();
|
||||
await afterNextRender(() => afterEvent({
|
||||
eventName: 'o-thread-cache-loaded-messages',
|
||||
func: () => document.querySelector('.o_MessagingMenu_toggler').click(),
|
||||
message: "should wait until inbox loaded initial needaction messages",
|
||||
predicate: ({ threadCache }) => {
|
||||
return threadCache.thread === messaging.inbox.thread;
|
||||
},
|
||||
}));
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ChannelPreviewView_ratingText').textContent,
|
||||
"Rating:",
|
||||
"should display the correct content (Rating:)"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ChannelPreviewView_ratingImage',
|
||||
"should have a rating image in the body"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$('.o_ChannelPreviewView_ratingImage').attr('data-src'),
|
||||
"/rating/static/src/img/rating_5.png",
|
||||
"should contain the correct rating image"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$('.o_ChannelPreviewView_ratingImage').attr('data-alt'),
|
||||
"top",
|
||||
"should contain the correct rating text"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { afterNextRender, start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('test_mail_full', {}, function () {
|
||||
QUnit.module('thread_needaction_preview_tests.js');
|
||||
|
||||
QUnit.test('rating value displayed on the thread needaction preview', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({});
|
||||
const mailTestRating1 = pyEnv['mail.test.rating'].create({});
|
||||
const mailMessageId1 = pyEnv['mail.message'].create({
|
||||
model: 'mail.test.rating',
|
||||
needaction: true,
|
||||
needaction_partner_ids: [pyEnv.currentPartnerId],
|
||||
res_id: mailTestRating1,
|
||||
});
|
||||
pyEnv['mail.notification'].create({
|
||||
mail_message_id: mailMessageId1,
|
||||
notification_status: 'sent',
|
||||
notification_type: 'inbox',
|
||||
res_partner_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
pyEnv['rating.rating'].create([{
|
||||
consumed: true,
|
||||
message_id: mailMessageId1,
|
||||
partner_id: resPartnerId1,
|
||||
rating_image_url: "/rating/static/src/img/rating_5.png",
|
||||
rating_text: "top",
|
||||
}]);
|
||||
const { afterEvent, messaging } = await start();
|
||||
await afterNextRender(() => afterEvent({
|
||||
eventName: 'o-thread-cache-loaded-messages',
|
||||
func: () => document.querySelector('.o_MessagingMenu_toggler').click(),
|
||||
message: "should wait until inbox loaded initial needaction messages",
|
||||
predicate: ({ threadCache }) => {
|
||||
return threadCache.thread === messaging.inbox.thread;
|
||||
},
|
||||
}));
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ThreadNeedactionPreview_ratingText').textContent,
|
||||
"Rating:",
|
||||
"should display the correct content (Rating:)"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ThreadNeedactionPreview_ratingImage',
|
||||
"should have a rating image in the body"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$('.o_ThreadNeedactionPreview_ratingImage').attr('data-src'),
|
||||
"/rating/static/src/img/rating_5.png",
|
||||
"should contain the correct rating image"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$('.o_ThreadNeedactionPreview_ratingImage').attr('data-alt'),
|
||||
"top",
|
||||
"should contain the correct rating text"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_odoobot
|
||||
from . import test_mail_performance
|
||||
from . import test_mail_thread_internals
|
||||
from . import test_mass_mailing
|
||||
from . import test_portal
|
||||
from . import test_rating
|
||||
from . import test_res_users
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
|
||||
|
||||
class TestMailFullCommon(TestMassMailCommon):
|
||||
""" Keep a single entry point, notably for backward compatibility """
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mail.tests.test_performance import BaseMailPerformance
|
||||
from odoo.tests.common import users, warmup
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mail_performance', 'post_install', '-at_install')
|
||||
class TestMailPerformance(BaseMailPerformance):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailPerformance, cls).setUpClass()
|
||||
|
||||
# users / followers
|
||||
cls.user_emp_email = mail_new_test_user(
|
||||
cls.env,
|
||||
company_id=cls.user_admin.company_id.id,
|
||||
company_ids=[(4, cls.user_admin.company_id.id)],
|
||||
email='user.emp.email@test.example.com',
|
||||
login='user_emp_email',
|
||||
groups='base.group_user,base.group_partner_manager',
|
||||
name='Emmanuel Email',
|
||||
notification_type='email',
|
||||
signature='--\nEmmanuel',
|
||||
)
|
||||
cls.user_portal = mail_new_test_user(
|
||||
cls.env,
|
||||
company_id=cls.user_admin.company_id.id,
|
||||
company_ids=[(4, cls.user_admin.company_id.id)],
|
||||
email='user.portal@test.example.com',
|
||||
login='user_portal',
|
||||
groups='base.group_portal',
|
||||
name='Paul Portal',
|
||||
)
|
||||
cls.customers = cls.env['res.partner'].create([
|
||||
{'country_id': cls.env.ref('base.be').id,
|
||||
'email': 'customer.full.test.1@example.com',
|
||||
'name': 'Test Full Customer 1',
|
||||
'mobile': '0456112233',
|
||||
'phone': '0456112233',
|
||||
},
|
||||
{'country_id': cls.env.ref('base.be').id,
|
||||
'email': 'customer.full.test.2@example.com',
|
||||
'name': 'Test Full Customer 2',
|
||||
'mobile': '0456223344',
|
||||
'phone': '0456112233',
|
||||
},
|
||||
])
|
||||
|
||||
# record
|
||||
cls.record_container = cls.env['mail.test.container.mc'].create({
|
||||
'alias_name': 'test-alias',
|
||||
'customer_id': cls.customer.id,
|
||||
'name': 'Test Container',
|
||||
})
|
||||
cls.record_ticket = cls.env['mail.test.ticket.mc'].create({
|
||||
'email_from': 'email.from@test.example.com',
|
||||
'container_id': cls.record_container.id,
|
||||
'customer_id': False,
|
||||
'name': 'Test Ticket',
|
||||
'user_id': cls.user_emp_email.id,
|
||||
})
|
||||
cls.record_ticket.message_subscribe(cls.customers.ids + cls.user_admin.partner_id.ids + cls.user_portal.partner_id.ids)
|
||||
|
||||
def test_initial_values(self):
|
||||
""" Simply ensure some values through all tests """
|
||||
record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket.ids)
|
||||
self.assertEqual(record_ticket.message_partner_ids,
|
||||
self.user_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_portal.partner_id)
|
||||
self.assertEqual(len(record_ticket.message_ids), 1)
|
||||
|
||||
@mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_message_post_w_followers(self):
|
||||
""" Aims to cover as much features of message_post as possible """
|
||||
record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket.ids)
|
||||
attachments = self.env['ir.attachment'].create(self.test_attachments_vals)
|
||||
|
||||
with self.assertQueryCount(employee=91): # tmf: 60
|
||||
new_message = record_ticket.message_post(
|
||||
attachment_ids=attachments.ids,
|
||||
body='<p>Test Content</p>',
|
||||
message_type='comment',
|
||||
subject='Test Subject',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
new_message.notified_partner_ids,
|
||||
self.user_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_portal.partner_id
|
||||
)
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.urls import url_parse
|
||||
|
||||
from odoo.addons.test_mail_full.tests.common import TestMailFullCommon
|
||||
from odoo.addons.test_mail_sms.tests.common import TestSMSRecipients
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
class TestMailThreadInternalsCommon(TestMailFullCommon, TestSMSRecipients):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailThreadInternalsCommon, cls).setUpClass()
|
||||
|
||||
cls.test_portal_records, cls.test_portal_partners = cls._create_records_for_batch(
|
||||
'mail.test.portal',
|
||||
2,
|
||||
)
|
||||
cls.test_portal_nop_records, _ = cls._create_records_for_batch(
|
||||
'mail.test.portal.no.partner',
|
||||
2,
|
||||
)
|
||||
cls.test_rating_records, cls.test_rating_partners = cls._create_records_for_batch(
|
||||
'mail.test.rating',
|
||||
2,
|
||||
)
|
||||
cls.test_simple_records, _ = cls._create_records_for_batch(
|
||||
'mail.test.simple',
|
||||
2,
|
||||
)
|
||||
|
||||
|
||||
@tagged('mail_thread', 'portal')
|
||||
class TestMailThreadInternals(TestMailThreadInternalsCommon):
|
||||
|
||||
@users('employee')
|
||||
def test_notify_get_recipients_groups(self):
|
||||
""" Test redirection of portal-enabled records """
|
||||
test_records = [
|
||||
self.test_portal_records[0].with_env(self.env),
|
||||
self.test_portal_nop_records[0].with_env(self.env),
|
||||
self.test_rating_records[0].with_env(self.env),
|
||||
self.test_simple_records[0].with_env(self.env),
|
||||
]
|
||||
for test_record in test_records:
|
||||
with self.subTest(test_record=test_record):
|
||||
is_portal = test_record._name != 'mail.test.simple'
|
||||
has_customer = test_record._name != 'mail.test.portal.no.partner'
|
||||
partner_fnames = test_record._mail_get_partner_fields()
|
||||
|
||||
if is_portal:
|
||||
self.assertFalse(
|
||||
test_record.access_token,
|
||||
'By default access tokens are False with portal'
|
||||
)
|
||||
|
||||
groups = test_record._notify_get_recipients_groups()
|
||||
portal_customer_group = next(
|
||||
(group for group in groups if group[0] == 'portal_customer'),
|
||||
False
|
||||
)
|
||||
|
||||
if is_portal and has_customer:
|
||||
# should have generated the access token, required for portal links
|
||||
self.assertTrue(
|
||||
test_record.access_token,
|
||||
'Portal should generate access token'
|
||||
)
|
||||
# check portal_customer content and link
|
||||
self.assertTrue(
|
||||
portal_customer_group,
|
||||
'Portal Mixin should add portal customer notification group'
|
||||
)
|
||||
portal_url = portal_customer_group[2]['button_access']['url']
|
||||
parameters = url_parse(portal_url).decode_query()
|
||||
self.assertEqual(parameters['access_token'], test_record.access_token)
|
||||
self.assertEqual(parameters['model'], test_record._name)
|
||||
self.assertEqual(parameters['pid'], str(test_record[partner_fnames[0]].id))
|
||||
self.assertEqual(parameters['res_id'], str(test_record.id))
|
||||
else:
|
||||
self.assertFalse(
|
||||
portal_customer_group,
|
||||
'Portal Mixin should not add portal customer notification group'
|
||||
)
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import werkzeug
|
||||
|
||||
from odoo.addons.test_mail_full.tests.common import TestMailFullCommon
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('mass_mailing')
|
||||
class TestMassMailing(TestMailFullCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_blacklist_opt_out(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
mailing.write({'subject': 'Subject {{ object.name }}'})
|
||||
|
||||
mailing.write({'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id})
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.optout', count=10)
|
||||
|
||||
# optout records 1 and 2
|
||||
(recipients[1] | recipients[2]).write({'opt_out': True})
|
||||
recipients[1].email_from = f'"Format Me" <{recipients[1].email_from}>'
|
||||
# blacklist records 3 and 4
|
||||
self.env['mail.blacklist'].create({'email': recipients[3].email_normalized})
|
||||
self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
|
||||
recipients[3].email_from = f'"Format Me" <{recipients[3].email_from}>'
|
||||
# have a duplicate email for 9
|
||||
recipients[9].email_from = f'"Format Me" <{recipients[9].email_from}>'
|
||||
recipient_dup_1 = recipients[9].copy()
|
||||
recipient_dup_1.email_from = f'"Format Me" <{recipient_dup_1.email_from}>'
|
||||
# have another duplicate for 9, but with multi emails already done
|
||||
recipient_dup_2 = recipients[9].copy()
|
||||
recipient_dup_2.email_from += f'; "TestDupe" <{recipients[8].email_from}>'
|
||||
# have another duplicate for 9, but with multi emails, one is different
|
||||
recipient_dup_3 = recipients[9].copy() # this one will passthrough (best-effort)
|
||||
recipient_dup_3.email_from += '; "TestMulti" <test.multi@test.example.com>'
|
||||
recipient_dup_4 = recipient_dup_2.copy() # this one will be discarded (youpi)
|
||||
# have a void mail
|
||||
recipient_void_1 = self.env['mailing.test.optout'].create({'name': 'TestRecord_void_1'})
|
||||
# have a falsy mail
|
||||
recipient_falsy_1 = self.env['mailing.test.optout'].create({
|
||||
'name': 'TestRecord_falsy_1',
|
||||
'email_from': 'falsymail'
|
||||
})
|
||||
recipients_all = (
|
||||
recipients + recipient_dup_1 + recipient_dup_2 + recipient_dup_3 + recipient_dup_4
|
||||
+ recipient_void_1 + recipient_falsy_1
|
||||
)
|
||||
|
||||
mailing.write({'mailing_domain': [('id', 'in', recipients_all.ids)]})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
for recipient in recipients_all:
|
||||
recipient_info = {
|
||||
'email': recipient.email_normalized,
|
||||
'content': f'Hello {recipient.name}',
|
||||
'mail_values': {
|
||||
'subject': f'Subject {recipient.name}',
|
||||
},
|
||||
}
|
||||
|
||||
# opt-out: cancel (cancel mail)
|
||||
if recipient in recipients[1] | recipients[2]:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_optout"
|
||||
# blacklisted: cancel (cancel mail)
|
||||
elif recipient in recipients[3] | recipients[4]:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_bl"
|
||||
# duplicates: cancel (cancel mail)
|
||||
elif recipient in (recipient_dup_1, recipient_dup_2, recipient_dup_4):
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_dup"
|
||||
# void: error (failed mail)
|
||||
elif recipient == recipient_void_1:
|
||||
recipient_info['trace_status'] = 'cancel'
|
||||
recipient_info['failure_type'] = "mail_email_missing"
|
||||
# falsy: error (failed mail)
|
||||
elif recipient == recipient_falsy_1:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_email_invalid"
|
||||
recipient_info['email'] = recipient.email_from # normalized is False but email should be falsymail
|
||||
else:
|
||||
# multi email -> outgoing email contains all emails
|
||||
if recipient == recipient_dup_3:
|
||||
email = self._find_sent_email(self.user_marketing.email_formatted, ['test.record.09@test.example.com', 'test.multi@test.example.com'])
|
||||
else:
|
||||
email = self._find_sent_email(self.user_marketing.email_formatted, [recipient.email_normalized])
|
||||
# preview correctly integrated rendered qweb
|
||||
self.assertIn(
|
||||
'Hi %s :)' % recipient.name,
|
||||
email['body'])
|
||||
# rendered unsubscribe
|
||||
self.assertIn(
|
||||
'%s/mailing/%s/confirm_unsubscribe' % (mailing.get_base_url(), mailing.id),
|
||||
email['body'])
|
||||
unsubscribe_href = self._get_href_from_anchor_id(email['body'], "url6")
|
||||
unsubscribe_url = werkzeug.urls.url_parse(unsubscribe_href)
|
||||
unsubscribe_params = unsubscribe_url.decode_query().to_dict(flat=True)
|
||||
self.assertEqual(int(unsubscribe_params['res_id']), recipient.id)
|
||||
self.assertEqual(unsubscribe_params['email'], recipient.email_normalized)
|
||||
self.assertEqual(
|
||||
mailing._unsubscribe_token(unsubscribe_params['res_id'], (unsubscribe_params['email'])),
|
||||
unsubscribe_params['token']
|
||||
)
|
||||
# rendered view
|
||||
self.assertIn(
|
||||
'%s/mailing/%s/view' % (mailing.get_base_url(), mailing.id),
|
||||
email['body'])
|
||||
view_href = self._get_href_from_anchor_id(email['body'], "url6")
|
||||
view_url = werkzeug.urls.url_parse(view_href)
|
||||
view_params = view_url.decode_query().to_dict(flat=True)
|
||||
self.assertEqual(int(view_params['res_id']), recipient.id)
|
||||
self.assertEqual(view_params['email'], recipient.email_normalized)
|
||||
self.assertEqual(
|
||||
mailing._unsubscribe_token(view_params['res_id'], (view_params['email'])),
|
||||
view_params['token']
|
||||
)
|
||||
|
||||
self.assertMailTraces(
|
||||
[recipient_info], mailing, recipient,
|
||||
mail_links_info=[[
|
||||
('url0', 'https://www.odoo.tz/my/%s' % recipient.name, True, {}),
|
||||
('url1', 'https://www.odoo.be', True, {}),
|
||||
('url2', 'https://www.odoo.com', True, {}),
|
||||
('url3', 'https://www.odoo.eu', True, {}),
|
||||
('url4', 'https://www.example.com/foo/bar?baz=qux', True, {'baz': 'qux'}),
|
||||
('url5', '%s/event/dummy-event-0' % mailing.get_base_url(), True, {}),
|
||||
# view is not shortened and parsed at sending
|
||||
('url6', '%s/view' % mailing.get_base_url(), False, {}),
|
||||
('url7', 'mailto:test@odoo.com', False, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('url8', '%s/unsubscribe_from_list' % mailing.get_base_url(), False, {}),
|
||||
]],
|
||||
check_mail=True,
|
||||
)
|
||||
|
||||
# sent: 15, 2 bl, 3 opt-out, 3 invalid -> 7 remaining
|
||||
# ignored: 2 bl + 3 optout + 2 invalid + 1 duplicate; failed: 0
|
||||
self.assertMailingStatistics(mailing, expected=16, delivered=7, sent=7, canceled=9, failed=0)
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged("odoobot")
|
||||
class TestOdoobot(TestMailCommon, TestRecipients):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestOdoobot, cls).setUpClass()
|
||||
cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'})
|
||||
|
||||
cls.odoobot = cls.env.ref("base.partner_root")
|
||||
cls.message_post_default_kwargs = {
|
||||
'body': '',
|
||||
'attachment_ids': [],
|
||||
'message_type': 'comment',
|
||||
'partner_ids': [],
|
||||
'subtype_xmlid': 'mail.mt_comment'
|
||||
}
|
||||
cls.odoobot_ping_body = '<a href="http://odoo.com/web#model=res.partner&id=%s" class="o_mail_redirect" data-oe-id="%s" data-oe-model="res.partner" target="_blank">@OdooBot</a>' % (cls.odoobot.id, cls.odoobot.id)
|
||||
cls.test_record_employe = cls.test_record.with_user(cls.user_employee)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_fetch_listener(self):
|
||||
channel = self.user_employee.with_user(self.user_employee)._init_odoobot()
|
||||
odoobot = self.env.ref("base.partner_root")
|
||||
odoobot_in_fetch_listeners = self.env['mail.channel.member'].search([('channel_id', '=', channel.id), ('partner_id', '=', odoobot.id)])
|
||||
self.assertEqual(len(odoobot_in_fetch_listeners), 1, 'odoobot should appear only once in channel_fetch_listeners')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_odoobot_ping(self):
|
||||
kwargs = self.message_post_default_kwargs.copy()
|
||||
kwargs.update({'body': self.odoobot_ping_body, 'partner_ids': [self.odoobot.id, self.user_admin.partner_id.id]})
|
||||
|
||||
with patch('random.choice', lambda x: x[0]):
|
||||
self.assertNextMessage(
|
||||
self.test_record_employe.with_context({'mail_post_autofollow': True}).message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer=False
|
||||
)
|
||||
# Odoobot should not be a follower but user_employee and user_admin should
|
||||
follower = self.test_record.message_follower_ids.mapped('partner_id')
|
||||
self.assertNotIn(self.odoobot, follower)
|
||||
self.assertIn(self.user_employee.partner_id, follower)
|
||||
self.assertIn(self.user_admin.partner_id, follower)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_onboarding_flow(self):
|
||||
kwargs = self.message_post_default_kwargs.copy()
|
||||
channel = self.user_employee.with_user(self.user_employee)._init_odoobot()
|
||||
|
||||
kwargs['body'] = 'tagada 😊'
|
||||
last_message = self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer=("help",)
|
||||
)
|
||||
channel.execute_command_help()
|
||||
self.assertNextMessage(
|
||||
last_message, # no message will be post with command help, use last odoobot message instead
|
||||
sender=self.odoobot,
|
||||
answer=("@OdooBot",)
|
||||
)
|
||||
kwargs['body'] = ''
|
||||
kwargs['partner_ids'] = [self.env['ir.model.data']._xmlid_to_res_id("base.partner_root")]
|
||||
self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer=("attachment",)
|
||||
)
|
||||
kwargs['body'] = ''
|
||||
attachment = self.env['ir.attachment'].with_user(self.user_employee).create({
|
||||
'datas': 'bWlncmF0aW9uIHRlc3Q=',
|
||||
'name': 'picture_of_your_dog.doc',
|
||||
'res_model': 'mail.compose.message',
|
||||
})
|
||||
kwargs['attachment_ids'] = [attachment.id]
|
||||
# For the end of the flow, we only test that the state changed, but not to which
|
||||
# one since it depends on the intalled apps, which can add more steps (like livechat)
|
||||
channel.message_post(**kwargs)
|
||||
self.assertNotEqual(self.user_employee.odoobot_state, 'onboarding_attachement')
|
||||
|
||||
# Test miscellaneous messages
|
||||
self.user_employee.odoobot_state = "idle"
|
||||
kwargs['partner_ids'] = []
|
||||
kwargs['body'] = "I love you"
|
||||
self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer=("too human for me",)
|
||||
)
|
||||
kwargs['body'] = "Go fuck yourself"
|
||||
self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer=("I have feelings",)
|
||||
)
|
||||
kwargs['body'] = "help me"
|
||||
self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer=("If you need help",)
|
||||
)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_odoobot_no_default_answer(self):
|
||||
kwargs = self.message_post_default_kwargs.copy()
|
||||
kwargs.update({'body': "I'm not talking to @odoobot right now", 'partner_ids': []})
|
||||
self.assertNextMessage(
|
||||
self.test_record_employe.message_post(**kwargs),
|
||||
answer=False
|
||||
)
|
||||
|
||||
def assertNextMessage(self, message, answer=None, sender=None):
|
||||
last_message = self.env['mail.message'].search([('id', '=', message.id + 1)])
|
||||
if last_message:
|
||||
body = last_message.body.replace('<p>', '').replace('</p>', '')
|
||||
else:
|
||||
self.assertFalse(answer, "No last message found when an answer was expect")
|
||||
if answer is not None:
|
||||
if answer and not last_message:
|
||||
self.assertTrue(False, "No last message found")
|
||||
if isinstance(answer, list):
|
||||
self.assertIn(body, answer)
|
||||
elif isinstance(answer, tuple):
|
||||
for elem in answer:
|
||||
self.assertIn(elem, body)
|
||||
elif not answer:
|
||||
self.assertFalse(last_message, "No answer should have been post")
|
||||
return
|
||||
else:
|
||||
self.assertEqual(body, answer)
|
||||
if sender:
|
||||
self.assertEqual(sender, last_message.author_id)
|
||||
return last_message
|
||||
|
|
@ -0,0 +1,465 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.urls import url_parse, url_decode, url_encode
|
||||
import json
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.test_mail_full.tests.common import TestMailFullCommon
|
||||
from odoo.addons.test_mail_sms.tests.common import TestSMSRecipients
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests import tagged, users
|
||||
from odoo.tests.common import HttpCase
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('portal')
|
||||
class TestPortal(HttpCase, TestMailFullCommon, TestSMSRecipients):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPortal, self).setUp()
|
||||
|
||||
self.record_portal = self.env['mail.test.portal'].create({
|
||||
'partner_id': self.partner_1.id,
|
||||
'name': 'Test Portal Record',
|
||||
})
|
||||
|
||||
self.record_portal._portal_ensure_token()
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'portal', 'mail_controller')
|
||||
class TestPortalControllers(TestPortal):
|
||||
|
||||
def test_portal_avatar_with_access_token(self):
|
||||
mail_record = self.env['mail.message'].create({
|
||||
'author_id': self.record_portal.partner_id.id,
|
||||
'model': self.record_portal._name,
|
||||
'res_id': self.record_portal.id,
|
||||
})
|
||||
response = self.url_open(f'/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50?access_token={self.record_portal.access_token}')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.headers.get('Content-Type'), 'image/png')
|
||||
self.assertRegex(response.headers.get('Content-Disposition', ''), r'mail_message-\d+-author_avatar\.png')
|
||||
|
||||
placeholder_response = self.url_open(f'/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50?access_token={self.record_portal.access_token + "a"}') # false token
|
||||
self.assertEqual(placeholder_response.status_code, 200)
|
||||
self.assertEqual(placeholder_response.headers.get('Content-Type'), 'image/png')
|
||||
self.assertRegex(placeholder_response.headers.get('Content-Disposition', ''), r'placeholder\.png')
|
||||
|
||||
no_token_response = self.url_open(f'/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50')
|
||||
self.assertEqual(no_token_response.status_code, 200)
|
||||
self.assertEqual(no_token_response.headers.get('Content-Type'), 'image/png')
|
||||
self.assertRegex(no_token_response.headers.get('Content-Disposition', ''), r'placeholder\.png')
|
||||
|
||||
def test_portal_avatar_with_hash_pid(self):
|
||||
self.authenticate(None, None)
|
||||
post_url = f"{self.record_portal.get_base_url()}/mail/chatter_post"
|
||||
res = self.opener.post(
|
||||
url=post_url,
|
||||
json={
|
||||
'params': {
|
||||
'csrf_token': http.Request.csrf_token(self),
|
||||
'message': 'Test',
|
||||
'res_model': self.record_portal._name,
|
||||
'res_id': self.record_portal.id,
|
||||
'hash': self.record_portal._sign_token(self.partner_2.id),
|
||||
'pid': self.partner_2.id,
|
||||
},
|
||||
},
|
||||
)
|
||||
res.raise_for_status()
|
||||
self.assertNotIn("error", res.json())
|
||||
message = self.record_portal.message_ids[0]
|
||||
response = self.url_open(
|
||||
f'/mail/avatar/mail.message/{message.id}/author_avatar/50x50?_hash={self.record_portal._sign_token(self.partner_2.id)}&pid={self.partner_2.id}')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.headers.get('Content-Type'), 'image/png')
|
||||
self.assertRegex(response.headers.get('Content-Disposition', ''), r'mail_message-\d+-author_avatar\.png')
|
||||
|
||||
placeholder_response = self.url_open(
|
||||
f'/mail/avatar/mail.message/{message.id}/author_avatar/50x50?_hash={self.record_portal._sign_token(self.partner_2.id) + "a"}&pid={self.partner_2.id}') # false hash
|
||||
self.assertEqual(placeholder_response.status_code, 200)
|
||||
self.assertEqual(placeholder_response.headers.get('Content-Type'), 'image/png')
|
||||
self.assertRegex(placeholder_response.headers.get('Content-Disposition', ''), r'placeholder\.png')
|
||||
|
||||
def test_portal_message_fetch(self):
|
||||
"""Test retrieving chatter messages through the portal controller"""
|
||||
self.authenticate(None, None)
|
||||
message_fetch_url = '/mail/chatter_fetch'
|
||||
payload = json.dumps({
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'call',
|
||||
'id': 0,
|
||||
'params': {
|
||||
'res_model': 'mail.test.portal',
|
||||
'res_id': self.record_portal.id,
|
||||
'token': self.record_portal.access_token,
|
||||
},
|
||||
})
|
||||
|
||||
def get_chatter_message_count():
|
||||
res = self.url_open(
|
||||
url=message_fetch_url,
|
||||
data=payload,
|
||||
headers={'Content-Type': 'application/json'}
|
||||
)
|
||||
return res.json().get('result', {}).get('message_count', 0)
|
||||
|
||||
self.assertEqual(get_chatter_message_count(), 0)
|
||||
|
||||
for _ in range(8):
|
||||
self.record_portal.message_post(
|
||||
body='Test',
|
||||
author_id=self.partner_1.id,
|
||||
message_type='comment',
|
||||
subtype_id=self.env.ref('mail.mt_comment').id,
|
||||
)
|
||||
|
||||
self.assertEqual(get_chatter_message_count(), 8)
|
||||
|
||||
# Empty the body of a few messages
|
||||
for i in (2, 5, 6):
|
||||
self.record_portal.message_ids[i].body = ""
|
||||
|
||||
# Empty messages should be ignored
|
||||
self.assertEqual(get_chatter_message_count(), 5)
|
||||
|
||||
def test_portal_share_comment(self):
|
||||
""" Test posting through portal controller allowing to use a hash to
|
||||
post wihtout access rights. """
|
||||
self.authenticate(None, None)
|
||||
post_url = f"{self.record_portal.get_base_url()}/mail/chatter_post"
|
||||
|
||||
# test as not logged
|
||||
self.opener.post(
|
||||
url=post_url,
|
||||
json={
|
||||
'params': {
|
||||
'csrf_token': http.Request.csrf_token(self),
|
||||
'hash': self.record_portal._sign_token(self.partner_2.id),
|
||||
'message': 'Test',
|
||||
'pid': self.partner_2.id,
|
||||
'redirect': '/',
|
||||
'res_model': self.record_portal._name,
|
||||
'res_id': self.record_portal.id,
|
||||
'token': self.record_portal.access_token,
|
||||
},
|
||||
},
|
||||
)
|
||||
message = self.record_portal.message_ids[0]
|
||||
|
||||
self.assertIn('Test', message.body)
|
||||
self.assertEqual(message.author_id, self.partner_2)
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'portal', 'mail_controller')
|
||||
class TestPortalFlow(TestMailFullCommon, HttpCase):
|
||||
""" Test shared links, mail/view links and redirection (backend, customer
|
||||
portal or frontend for specific addons). """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.customer = cls.env['res.partner'].create({
|
||||
'country_id': cls.env.ref('base.fr').id,
|
||||
'email': 'mdelvaux34@example.com',
|
||||
'lang': 'en_US',
|
||||
'mobile': '+33639982325',
|
||||
'name': 'Mathias Delvaux',
|
||||
'phone': '+33353011823',
|
||||
})
|
||||
# customer portal enabled
|
||||
cls.record_portal = cls.env['mail.test.portal'].create({
|
||||
'name': 'Test Portal Record',
|
||||
'partner_id': cls.customer.id,
|
||||
'user_id': cls.user_admin.id,
|
||||
})
|
||||
# internal only
|
||||
cls.record_internal = cls.env['mail.test.track'].create({
|
||||
'name': 'Test Internal Record',
|
||||
})
|
||||
# readable (aka portal can read but no specific action)
|
||||
cls.record_read = cls.env['mail.test.simple'].create({
|
||||
'name': 'Test Readable Record',
|
||||
})
|
||||
# 'public' target_type act_url (e.g. blog, forum, ...) -> redirection to a public page
|
||||
cls.record_public_act_url = cls.env['mail.test.portal.public.access.action'].create({
|
||||
'name': 'Public ActUrl',
|
||||
})
|
||||
|
||||
cls.mail_template = cls.env['mail.template'].create({
|
||||
'auto_delete': True,
|
||||
'body_html': '<p>Hello <t t-out="object.partner_id.name"/>, your quotation is ready for review.</p>',
|
||||
'email_from': '{{ (object.user_id.email_formatted or user.email_formatted) }}',
|
||||
'model_id': cls.env.ref('test_mail_full.model_mail_test_portal').id,
|
||||
'name': 'Quotation template',
|
||||
'partner_to': '{{ object.partner_id.id }}',
|
||||
'subject': 'Your quotation "{{ object.name }}"',
|
||||
})
|
||||
cls._create_portal_user()
|
||||
|
||||
# prepare access URLs on self to ease tests
|
||||
# ------------------------------------------------------------
|
||||
base_url = cls.record_portal.get_base_url()
|
||||
cls.test_base_url = base_url
|
||||
|
||||
cls.record_internal_url_base = f'{base_url}/mail/view?model={cls.record_internal._name}&res_id={cls.record_internal.id}'
|
||||
cls.record_portal_url_base = f'{base_url}/mail/view?model={cls.record_portal._name}&res_id={cls.record_portal.id}'
|
||||
cls.record_read_url_base = f'{base_url}/mail/view?model={cls.record_read._name}&res_id={cls.record_read.id}'
|
||||
cls.record_public_act_url_base = f'{base_url}/mail/view?model={cls.record_public_act_url._name}&res_id={cls.record_public_act_url.id}'
|
||||
|
||||
max_internal_id = cls.env['mail.test.track'].search([], order="id desc", limit=1).id
|
||||
max_portal_id = cls.env['mail.test.portal'].search([], order="id desc", limit=1).id
|
||||
max_read_id = cls.env['mail.test.simple'].search([], order="id desc", limit=1).id
|
||||
max_public_act_url_id = cls.env['mail.test.portal.public.access.action'].search([], order="id desc", limit=1).id
|
||||
cls.record_internal_url_no_exists = f'{base_url}/mail/view?model={cls.record_internal._name}&res_id={max_internal_id + 1}'
|
||||
cls.record_portal_url_no_exists = f'{base_url}/mail/view?model={cls.record_portal._name}&res_id={max_portal_id + 1}'
|
||||
cls.record_read_url_no_exists = f'{base_url}/mail/view?model={cls.record_read._name}&res_id={max_read_id + 1}'
|
||||
cls.record_public_act_url_url_no_exists = f'{base_url}/mail/view?model={cls.record_public_act_url._name}&res_id={max_public_act_url_id + 1}'
|
||||
|
||||
cls.record_url_no_model = f'{cls.record_portal.get_base_url()}/mail/view?model=this.should.not.exists&res_id=1'
|
||||
|
||||
# find portal + auth data url
|
||||
for group_name, group_func, group_data in cls.record_portal.sudo()._notify_get_recipients_groups(False):
|
||||
if group_name == 'portal_customer' and group_func(cls.customer):
|
||||
cls.record_portal_url_auth = group_data['button_access']['url']
|
||||
break
|
||||
else:
|
||||
raise AssertionError('Record access URL not found')
|
||||
# build altered access_token URL for testing security
|
||||
parsed_url = url_parse(cls.record_portal_url_auth)
|
||||
query_params = url_decode(parsed_url.query)
|
||||
cls.record_portal_hash = query_params['hash']
|
||||
cls.record_portal_url_auth_wrong_token = parsed_url.replace(
|
||||
query=url_encode({
|
||||
**query_params,
|
||||
'access_token': query_params['access_token'].translate(
|
||||
str.maketrans('0123456789abcdef', '9876543210fedcba')
|
||||
)
|
||||
}, sort=True)
|
||||
).to_url()
|
||||
|
||||
# prepare result URLs on self to ease tests
|
||||
# ------------------------------------------------------------
|
||||
cls.portal_web_url = f'{base_url}/my/test_portal/{cls.record_portal.id}'
|
||||
cls.portal_web_url_with_token = f'{base_url}/my/test_portal/{cls.record_portal.id}?{url_encode({"access_token": cls.record_portal.access_token, "pid": cls.customer.id, "hash": cls.record_portal_hash}, sort=True)}'
|
||||
cls.public_act_url_share = f'{base_url}/test_portal/public_type/{cls.record_public_act_url.id}'
|
||||
cls.internal_backend_local_url = f'/web#{url_encode({"model": cls.record_internal._name, "id": cls.record_internal.id, "active_id": cls.record_internal.id, "cids": cls.company_admin.id}, sort=True)}'
|
||||
cls.portal_backend_local_url = f'/web#{url_encode({"model": cls.record_portal._name, "id": cls.record_portal.id, "active_id": cls.record_portal.id, "cids": cls.company_admin.id}, sort=True)}'
|
||||
cls.read_backend_local_url = f'/web#{url_encode({"model": cls.record_read._name, "id": cls.record_read.id, "active_id": cls.record_read.id, "cids": cls.company_admin.id}, sort=True)}'
|
||||
cls.public_act_url_backend_local_url = f'/web#{url_encode({"model": cls.record_public_act_url._name, "id": cls.record_public_act_url.id, "active_id": cls.record_public_act_url.id, "cids": cls.company_admin.id}, sort=True)}'
|
||||
cls.discuss_local_url = '/web#action=mail.action_discuss'
|
||||
|
||||
def test_assert_initial_data(self):
|
||||
""" Test some initial values. Test that record_access_url is a valid URL
|
||||
to view the record_portal and that record_access_url_wrong_token only differs
|
||||
from record_access_url by a different access_token. """
|
||||
self.record_internal.with_user(self.user_employee).check_access_rule('read')
|
||||
self.record_portal.with_user(self.user_employee).check_access_rule('read')
|
||||
self.record_read.with_user(self.user_employee).check_access_rule('read')
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.record_internal.with_user(self.user_portal).check_access_rights('read')
|
||||
with self.assertRaises(AccessError):
|
||||
self.record_portal.with_user(self.user_portal).check_access_rights('read')
|
||||
self.record_read.with_user(self.user_portal).check_access_rights('read')
|
||||
|
||||
self.assertNotEqual(self.record_portal_url_auth, self.record_portal_url_auth_wrong_token)
|
||||
url_params = []
|
||||
for url in (
|
||||
self.record_portal_url_auth, self.record_portal_url_auth_wrong_token,
|
||||
):
|
||||
with self.subTest(url=url):
|
||||
parsed = url_parse(url)
|
||||
self.assertEqual(parsed.path, '/mail/view')
|
||||
params = url_decode(parsed.query)
|
||||
url_params.append(params)
|
||||
# Note that pid, hash and auth_signup_token are not tested by this test but may be present in the URL (config).
|
||||
self.assertEqual(params.get('model'), 'mail.test.portal')
|
||||
self.assertEqual(int(params.get('res_id')), self.record_portal.id)
|
||||
self.assertTrue(params.get('access_token'))
|
||||
self.assertNotEqual(url_params[0]['access_token'], url_params[1]['access_token'])
|
||||
self.assertEqual(
|
||||
{k: v for k, v in url_params[0].items() if k != 'access_token'},
|
||||
{k: v for k, v in url_params[1].items() if k != 'access_token'},
|
||||
'URLs should be the same, except for access token'
|
||||
)
|
||||
|
||||
@users('employee')
|
||||
def test_employee_access(self):
|
||||
""" Check internal employee behavior when accessing mail/view """
|
||||
self.authenticate(self.env.user.login, self.env.user.login)
|
||||
for url_name, url, exp_url in [
|
||||
# accessible records
|
||||
("Internal record mail/view", self.record_internal_url_base, self.internal_backend_local_url),
|
||||
("Portal record mail/view", self.record_portal_url_base, self.portal_backend_local_url),
|
||||
("Portal readable record mail/view", self.record_read_url_base, self.read_backend_local_url),
|
||||
("Public with act_url", self.record_public_act_url_base, self.public_act_url_backend_local_url),
|
||||
# even with token -> backend
|
||||
("Portal record with token", self.record_portal_url_auth, self.portal_backend_local_url),
|
||||
# invalid token is not an issue for employee -> backend, has access
|
||||
("Portal record with wrong token", self.record_portal_url_auth_wrong_token, self.portal_backend_local_url),
|
||||
# not existing -> redirect to discuss
|
||||
("Not existing record (internal)", self.record_internal_url_no_exists, self.discuss_local_url),
|
||||
("Not existing record (portal enabled)", self.record_portal_url_no_exists, self.discuss_local_url),
|
||||
("Not existign model", self.record_url_no_model, self.discuss_local_url),
|
||||
]:
|
||||
with self.subTest(name=url_name, url=url):
|
||||
res = self.url_open(url)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertURLEqual(res.url, exp_url)
|
||||
|
||||
@mute_logger('werkzeug')
|
||||
@users('portal_test')
|
||||
def test_portal_access_logged(self):
|
||||
""" Check portal behavior when accessing mail/view, notably check token
|
||||
support and propagation. """
|
||||
my_url = f'{self.test_base_url}/my'
|
||||
|
||||
self.authenticate(self.env.user.login, self.env.user.login)
|
||||
for url_name, url, exp_url in [
|
||||
# valid token -> ok -> redirect to portal URL
|
||||
(
|
||||
"No access (portal enabled), token", self.record_portal_url_auth,
|
||||
self.portal_web_url_with_token,
|
||||
),
|
||||
# invalid token -> ko -> redirect to my
|
||||
(
|
||||
"No access (portal enabled), invalid token", self.record_portal_url_auth_wrong_token,
|
||||
my_url,
|
||||
),
|
||||
# std url, read record -> redirect to my with parameters being record portal action parameters (???)
|
||||
(
|
||||
'Access record (no customer portal)', self.record_read_url_base,
|
||||
f'{self.test_base_url}/my#{url_encode({"model": self.record_read._name, "id": self.record_read.id, "active_id": self.record_read.id, "cids": self.company_admin.id}, sort=True)}',
|
||||
),
|
||||
# std url, no access to record -> redirect to my
|
||||
(
|
||||
'No access record (internal)', self.record_internal_url_base,
|
||||
my_url,
|
||||
),
|
||||
# missing token -> redirect to my
|
||||
(
|
||||
'No access record (portal enabled)', self.record_portal_url_base,
|
||||
my_url,
|
||||
),
|
||||
# public_type act_url -> share users are redirected to frontend url
|
||||
(
|
||||
"Public with act_url -> frontend url", self.record_public_act_url_base,
|
||||
self.public_act_url_share
|
||||
),
|
||||
# not existing -> redirect to my
|
||||
(
|
||||
'Not existing record (internal)', self.record_internal_url_no_exists,
|
||||
my_url,
|
||||
),
|
||||
(
|
||||
'Not existing record (portal enabled)', self.record_portal_url_no_exists,
|
||||
my_url,
|
||||
),
|
||||
(
|
||||
'Not existing model', self.record_url_no_model,
|
||||
my_url,
|
||||
),
|
||||
]:
|
||||
with self.subTest(name=url_name, url=url):
|
||||
res = self.url_open(url)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertURLEqual(res.url, exp_url)
|
||||
|
||||
@mute_logger('werkzeug')
|
||||
def test_portal_access_not_logged(self):
|
||||
""" Check customer behavior when accessing mail/view, notably check token
|
||||
support and propagation. """
|
||||
self.authenticate(None, None)
|
||||
login_url = f'{self.test_base_url}/web/login'
|
||||
|
||||
for url_name, url, exp_url in [
|
||||
# valid token -> ok -> redirect to portal URL
|
||||
(
|
||||
"No access (portal enabled), token", self.record_portal_url_auth,
|
||||
self.portal_web_url_with_token,
|
||||
),
|
||||
# invalid token -> ko -> redirect to login with redirect to original link, will be rejected after login
|
||||
(
|
||||
"No access (portal enabled), invalid token", self.record_portal_url_auth_wrong_token,
|
||||
f'{login_url}?{url_encode({"redirect": self.record_portal_url_auth_wrong_token.replace(self.test_base_url, "")})}',
|
||||
),
|
||||
# std url, no access to record -> redirect to login with redirect to original link, will be rejected after login
|
||||
(
|
||||
'No access record (internal)', self.record_internal_url_base,
|
||||
f'{login_url}?{url_encode({"redirect": self.record_internal_url_base.replace(self.test_base_url, "")})}',
|
||||
),
|
||||
# std url, no access to record but portal -> redirect to login, original (local) URL kept as redirection post login to try again (even if faulty)
|
||||
(
|
||||
'No access record (portal enabled)', self.record_portal_url_base,
|
||||
f'{login_url}?{url_encode({"redirect": self.record_portal_url_base.replace(self.test_base_url, "")})}',
|
||||
),
|
||||
(
|
||||
'No access record (portal can read, no customer portal)', self.record_read_url_base,
|
||||
f'{login_url}?{url_encode({"redirect": self.record_read_url_base.replace(self.test_base_url, "")})}',
|
||||
),
|
||||
# public_type act_url -> share users are redirected to frontend url
|
||||
(
|
||||
"Public with act_url -> frontend url", self.record_public_act_url_base,
|
||||
self.public_act_url_share
|
||||
),
|
||||
# not existing -> redirect to login, original (internal) URL kept as redirection post login to try again (even if faulty)
|
||||
(
|
||||
'Not existing record (internal)', self.record_internal_url_no_exists,
|
||||
f'{login_url}?{url_encode({"redirect": self.record_internal_url_no_exists.replace(self.test_base_url, "")})}',
|
||||
),
|
||||
(
|
||||
'Not existing record (portal enabled)', self.record_portal_url_no_exists,
|
||||
f'{login_url}?{url_encode({"redirect": self.record_portal_url_no_exists.replace(self.test_base_url, "")})}',
|
||||
),
|
||||
(
|
||||
'Not existing model', self.record_url_no_model,
|
||||
f'{login_url}?{url_encode({"redirect": self.record_url_no_model.replace(self.test_base_url, "")})}',
|
||||
),
|
||||
]:
|
||||
with self.subTest(name=url_name, url=url):
|
||||
res = self.url_open(url)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertURLEqual(res.url, exp_url)
|
||||
|
||||
def test_redirect_to_records_norecord(self):
|
||||
""" Check specific use case of missing model, should directly redirect
|
||||
to login page. """
|
||||
for model, res_id in [
|
||||
(False, self.record_portal.id),
|
||||
('', self.record_portal.id),
|
||||
(self.record_portal._name, False),
|
||||
(self.record_portal._name, ''),
|
||||
(False, False),
|
||||
('wrong.model', self.record_portal.id),
|
||||
(self.record_portal._name, -4),
|
||||
]:
|
||||
response = self.url_open(
|
||||
'/mail/view?model=%s&res_id=%s' % (model, res_id),
|
||||
timeout=15
|
||||
)
|
||||
path = url_parse(response.url).path
|
||||
self.assertEqual(
|
||||
path, '/web/login',
|
||||
'Failed with %s - %s' % (model, res_id)
|
||||
)
|
||||
|
||||
|
||||
@tagged('portal')
|
||||
class TestPortalMixin(TestPortal):
|
||||
|
||||
@users('employee')
|
||||
def test_portal_mixin(self):
|
||||
""" Test internals of portal mixin """
|
||||
customer = self.partner_1.with_env(self.env)
|
||||
record_portal = self.env['mail.test.portal'].create({
|
||||
'partner_id': customer.id,
|
||||
'name': 'Test Portal Record',
|
||||
})
|
||||
|
||||
self.assertFalse(record_portal.access_token)
|
||||
self.assertEqual(record_portal.access_url, '/my/test_portal/%s' % record_portal.id)
|
||||
|
||||
record_portal._portal_ensure_token()
|
||||
self.assertTrue(record_portal.access_token)
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import lxml
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.test_mail_full.tests.common import TestMailFullCommon
|
||||
from odoo.addons.test_mail_sms.tests.common import TestSMSRecipients
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import HttpCase, users, warmup
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestRatingCommon(TestMailFullCommon, TestSMSRecipients):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestRatingCommon, cls).setUpClass()
|
||||
|
||||
cls.record_rating = cls.env['mail.test.rating'].create({
|
||||
'customer_id': cls.partner_1.id,
|
||||
'name': 'Test Rating',
|
||||
'user_id': cls.user_admin.id,
|
||||
})
|
||||
|
||||
|
||||
@tagged('rating')
|
||||
class TestRatingFlow(TestRatingCommon):
|
||||
|
||||
def test_initial_values(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
self.assertFalse(record_rating.rating_ids)
|
||||
self.assertEqual(record_rating.message_partner_ids, self.partner_admin)
|
||||
self.assertEqual(len(record_rating.message_ids), 1)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_rating_prepare(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
|
||||
# check rating creation
|
||||
rating = record_rating.rating_ids
|
||||
self.assertEqual(rating.access_token, access_token)
|
||||
self.assertFalse(rating.consumed)
|
||||
self.assertFalse(rating.is_internal)
|
||||
self.assertEqual(rating.partner_id, self.partner_1)
|
||||
self.assertEqual(rating.rated_partner_id, self.user_admin.partner_id)
|
||||
self.assertFalse(rating.rating)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_rating_rating_apply(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
record_messages = record_rating.message_ids
|
||||
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
|
||||
# simulate an email click: notification should be delayed
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(5, token=access_token, feedback='Top Feedback', notify_delay_send=True)
|
||||
message = record_rating.message_ids[0]
|
||||
rating = record_rating.rating_ids
|
||||
|
||||
# check posted message
|
||||
self.assertEqual(record_rating.message_ids, record_messages + message)
|
||||
self.assertIn('Top Feedback', message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_5.png', message.body)
|
||||
self.assertEqual(message.author_id, self.partner_1)
|
||||
self.assertEqual(message.rating_ids, rating)
|
||||
self.assertFalse(message.notified_partner_ids)
|
||||
self.assertEqual(message.subtype_id, self.env.ref('test_mail_full.mt_mail_test_rating_rating_done'))
|
||||
|
||||
# check rating update
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.feedback, 'Top Feedback')
|
||||
self.assertEqual(rating.message_id, message)
|
||||
self.assertEqual(rating.rating, 5)
|
||||
self.assertEqual(record_rating.rating_last_value, 5)
|
||||
|
||||
# give a feedback: send notifications (notify_delay_send set to False)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(1, token=access_token, feedback='Bad Feedback')
|
||||
|
||||
# check posted message: message is updated
|
||||
update_message = record_rating.message_ids[0]
|
||||
self.assertEqual(update_message, message, 'Should update first message')
|
||||
self.assertEqual(record_rating.message_ids, record_messages + update_message)
|
||||
self.assertIn('Bad Feedback', update_message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_1.png', update_message.body)
|
||||
self.assertEqual(update_message.author_id, self.partner_1)
|
||||
self.assertEqual(update_message.rating_ids, rating)
|
||||
self.assertEqual(update_message.notified_partner_ids, self.partner_admin)
|
||||
self.assertEqual(update_message.subtype_id, self.env.ref("test_mail_full.mt_mail_test_rating_rating_done"))
|
||||
|
||||
# check rating update
|
||||
new_rating = record_rating.rating_ids
|
||||
self.assertEqual(new_rating, rating, 'Should update first rating')
|
||||
self.assertTrue(new_rating.consumed)
|
||||
self.assertEqual(new_rating.feedback, 'Bad Feedback')
|
||||
self.assertEqual(new_rating.message_id, update_message)
|
||||
self.assertEqual(new_rating.rating, 1)
|
||||
self.assertEqual(record_rating.rating_last_value, 1)
|
||||
|
||||
|
||||
@tagged('rating')
|
||||
class TestRatingMixin(TestRatingCommon):
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_values(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
|
||||
# prepare rating token
|
||||
access_0 = record_rating._rating_get_access_token()
|
||||
last_rating = record_rating.rating_apply(3, token=access_0, feedback="This record is meh but it's cheap.")
|
||||
# Make sure to update the write_date which is used to retrieve the last rating
|
||||
last_rating.write_date = datetime(2022, 1, 1, 14, 00)
|
||||
access_1 = record_rating._rating_get_access_token()
|
||||
last_rating = record_rating.rating_apply(1, token=access_1, feedback="This record sucks so much. I want to speak to the manager !")
|
||||
last_rating.write_date = datetime(2022, 2, 1, 14, 00)
|
||||
access_2 = record_rating._rating_get_access_token()
|
||||
last_rating = record_rating.rating_apply(5, token=access_2, feedback="This is the best record ever ! I wish I read the documentation before complaining !")
|
||||
last_rating.write_date = datetime(2022, 3, 1, 14, 00)
|
||||
record_rating.rating_ids.flush_model(['write_date'])
|
||||
|
||||
self.assertEqual(record_rating.rating_last_value, 5, "The last rating is kept.")
|
||||
self.assertEqual(record_rating.rating_avg, 3, "The average should be equal to 3")
|
||||
|
||||
|
||||
@tagged('rating', 'mail_performance', 'post_install', '-at_install')
|
||||
class TestRatingPerformance(TestRatingCommon):
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_last_value_perfs(self):
|
||||
RECORD_COUNT = 100
|
||||
partners = self.env['res.partner'].sudo().create([
|
||||
{'name': 'Jean-Luc %s' % (idx), 'email': 'jean-luc-%s@opoo.com' % (idx)} for idx in range(RECORD_COUNT)])
|
||||
|
||||
with self.assertQueryCount(employee=1516): # tmf 1516 / com 5510
|
||||
record_ratings = self.env['mail.test.rating'].create([{
|
||||
'customer_id': partners[idx].id,
|
||||
'name': 'Test Rating',
|
||||
'user_id': self.user_admin.id,
|
||||
} for idx in range(RECORD_COUNT)])
|
||||
self.flush_tracking()
|
||||
|
||||
with self.assertQueryCount(employee=2004): # tmf 2004
|
||||
for record in record_ratings:
|
||||
access_token = record._rating_get_access_token()
|
||||
record.rating_apply(1, token=access_token)
|
||||
self.flush_tracking()
|
||||
|
||||
with self.assertQueryCount(employee=2003): # tmf 2003
|
||||
for record in record_ratings:
|
||||
access_token = record._rating_get_access_token()
|
||||
record.rating_apply(5, token=access_token)
|
||||
self.flush_tracking()
|
||||
|
||||
with self.assertQueryCount(employee=1):
|
||||
record_ratings._compute_rating_last_value()
|
||||
vals = [val == 5 for val in record_ratings.mapped('rating_last_value')]
|
||||
self.assertTrue(all(vals), "The last rating is kept.")
|
||||
|
||||
|
||||
@tagged('rating')
|
||||
class TestRatingRoutes(HttpCase, TestRatingCommon):
|
||||
|
||||
def test_open_rating_route(self):
|
||||
"""
|
||||
16.0 + expected behavior
|
||||
1) Clicking on the smiley image triggers the /rate/<string:token>/<int:rate>
|
||||
route should not update the rating of the record but simply redirect
|
||||
to the feedback form
|
||||
2) Customer interacts with webpage and submits FORM. Triggers /rate/<string:token>/submit_feedback
|
||||
route. Should update the rating of the record with the data in the POST request
|
||||
"""
|
||||
self.authenticate(None, None) # set up session for public user
|
||||
access_token = self.record_rating._rating_get_access_token()
|
||||
|
||||
# First round of clicking the URL and then submitting FORM data
|
||||
response_click_one = self.url_open(f"/rate/{access_token}/5")
|
||||
response_click_one.raise_for_status()
|
||||
|
||||
# there should be a form to post to validate the feedback and avoid one-click anyway
|
||||
forms = lxml.html.fromstring(response_click_one.content).xpath('//form')
|
||||
self.assertEqual(forms[0].get('method'), 'post')
|
||||
self.assertEqual(forms[0].get('action', ''), f'/rate/{access_token}/submit_feedback')
|
||||
|
||||
# rating should not change, i.e. default values
|
||||
rating = self.record_rating.rating_ids
|
||||
self.assertFalse(rating.consumed)
|
||||
self.assertEqual(rating.rating, 0)
|
||||
self.assertFalse(rating.feedback)
|
||||
self.assertEqual(self.record_rating.rating_last_value, 0)
|
||||
|
||||
response_submit_one = self.url_open(
|
||||
f"/rate/{access_token}/submit_feedback",
|
||||
data={
|
||||
"rate": 5,
|
||||
"csrf_token": http.Request.csrf_token(self),
|
||||
"feedback": "good",
|
||||
}
|
||||
)
|
||||
response_submit_one.raise_for_status()
|
||||
|
||||
rating_post_submit_one = self.record_rating.rating_ids
|
||||
self.assertTrue(rating_post_submit_one.consumed)
|
||||
self.assertEqual(rating_post_submit_one.rating, 5)
|
||||
self.assertEqual(rating_post_submit_one.feedback, "good")
|
||||
self.assertEqual(self.record_rating.rating_last_value, 5)
|
||||
|
||||
# Second round of clicking the URL and then submitting FORM data
|
||||
response_click_two = self.url_open(f"/rate/{access_token}/1")
|
||||
response_click_two.raise_for_status()
|
||||
self.assertEqual(self.record_rating.rating_last_value, 5) # should not be updated to 1
|
||||
|
||||
# check returned form
|
||||
forms = lxml.html.fromstring(response_click_two.content).xpath('//form')
|
||||
self.assertEqual(forms[0].get('method'), 'post')
|
||||
self.assertEqual(forms[0].get('action', ''), f'/rate/{access_token}/submit_feedback')
|
||||
|
||||
response_submit_two = self.url_open(f"/rate/{access_token}/submit_feedback",
|
||||
data={"rate": 1,
|
||||
"csrf_token": http.Request.csrf_token(self),
|
||||
"feedback": "bad job"})
|
||||
response_submit_two.raise_for_status()
|
||||
|
||||
rating_post_submit_second = self.record_rating.rating_ids
|
||||
self.assertTrue(rating_post_submit_second.consumed)
|
||||
self.assertEqual(rating_post_submit_second.rating, 1)
|
||||
self.assertEqual(rating_post_submit_second.feedback, "bad job")
|
||||
self.assertEqual(self.record_rating.rating_last_value, 1)
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mail_full.tests.common import TestMailFullCommon
|
||||
|
||||
|
||||
class TestResUsers(TestMailFullCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestResUsers, cls).setUpClass()
|
||||
cls.portal_user = mail_new_test_user(
|
||||
cls.env,
|
||||
login='portal_user',
|
||||
mobile='+32 494 12 34 56',
|
||||
phone='+32 494 12 34 89',
|
||||
password='password',
|
||||
name='Portal User',
|
||||
email='portal@test.example.com',
|
||||
groups='base.group_portal',
|
||||
)
|
||||
|
||||
cls.portal_user_2 = mail_new_test_user(
|
||||
cls.env,
|
||||
login='portal_user_2',
|
||||
mobile='+32 494 12 34 22',
|
||||
phone='invalid phone',
|
||||
password='password',
|
||||
name='Portal User 2',
|
||||
email='portal_2@test.example.com',
|
||||
groups='base.group_portal',
|
||||
)
|
||||
|
||||
# Remove existing blacklisted email / phone (they will be sanitized, so we avoid to sanitize them here)
|
||||
cls.env['mail.blacklist'].search([]).unlink()
|
||||
cls.env['phone.blacklist'].search([]).unlink()
|
||||
|
||||
def test_deactivate_portal_users_blacklist(self):
|
||||
"""Test that the email and the phone are blacklisted
|
||||
when a portal user deactivate his own account.
|
||||
"""
|
||||
(self.portal_user | self.portal_user_2)._deactivate_portal_user(request_blacklist=True)
|
||||
|
||||
self.assertFalse(self.portal_user.active, 'Should have archived the user')
|
||||
self.assertFalse(self.portal_user.partner_id.active, 'Should have archived the partner')
|
||||
self.assertFalse(self.portal_user_2.active, 'Should have archived the user')
|
||||
self.assertFalse(self.portal_user_2.partner_id.active, 'Should have archived the partner')
|
||||
|
||||
blacklist = self.env['mail.blacklist'].search([
|
||||
('email', 'in', ('portal@test.example.com', 'portal_2@test.example.com')),
|
||||
])
|
||||
self.assertEqual(len(blacklist), 2, 'Should have blacklisted the users email')
|
||||
|
||||
blacklists = self.env['phone.blacklist'].search([
|
||||
('number', 'in', ('+32494123489', '+32494123456', '+32494123422')),
|
||||
])
|
||||
self.assertEqual(len(blacklists), 3, 'Should have blacklisted the user phone and mobile')
|
||||
|
||||
blacklist = self.env['phone.blacklist'].search([('number', '=', 'invalid phone')])
|
||||
self.assertFalse(blacklist, 'Should have skipped invalid phone')
|
||||
|
||||
def test_deactivate_portal_users_no_blacklist(self):
|
||||
"""Test the case when the user do not want to blacklist his email / phone."""
|
||||
(self.portal_user | self.portal_user_2)._deactivate_portal_user(request_blacklist=False)
|
||||
|
||||
self.assertFalse(self.portal_user.active, 'Should have archived the user')
|
||||
self.assertFalse(self.portal_user.partner_id.active, 'Should have archived the partner')
|
||||
self.assertFalse(self.portal_user_2.active, 'Should have archived the user')
|
||||
self.assertFalse(self.portal_user_2.partner_id.active, 'Should have archived the partner')
|
||||
|
||||
blacklists = self.env['mail.blacklist'].search([])
|
||||
self.assertFalse(blacklists, 'Should not have blacklisted the users email')
|
||||
|
||||
blacklists = self.env['phone.blacklist'].search([])
|
||||
self.assertFalse(blacklists, 'Should not have blacklisted the user phone and mobile')
|
||||
Loading…
Add table
Add a link
Reference in a new issue