Compare commits
No commits in common. '14.0' and '13.0-mail_multi_website-publish' have entirely different histories.
14.0
...
13.0-mail_
-
2.DINAR/build-date.txt
-
5.DINAR/config.yaml
-
19.DINAR/image/src/addons.yaml
-
3.eslintrc.yml
-
145.github/workflows/DINAR-PORT.yml
-
5.github/workflows/DINAR-pr.yml
-
91.gitignore
-
3.isort.cfg
-
84.pre-commit-config.yaml
-
3.pylintrc
-
2.pylintrc-mandatory
-
47.travis.yml
-
23LICENSE
-
12README.md
-
1build-date.txt
-
49email_headers/README.md
-
32email_headers/__init__.py
-
41email_headers/__manifest__.py
-
14email_headers/data/ir_config_parameter_data.xml
-
9email_headers/migrations/12.0.1.2.0/post-migration.py
-
21email_headers/models/__init__.py
-
585email_headers/models/mail.py
-
1email_headers/tests/__init__.py
-
353email_headers/tests/test_email.py
-
22email_headers/views/ir_mail_server_views.xml
-
29mail_all/README.rst
-
1mail_all/__init__.py
-
30mail_all/__manifest__.py
-
9mail_all/doc/changelog.rst
-
19mail_all/doc/index.rst
-
70mail_all/i18n/de.po
-
69mail_all/i18n/es.po
-
64mail_all/i18n/mail_all.pot
-
35mail_all/i18n/sl.po
-
BINmail_all/images/1.jpg
-
BINmail_all/static/description/1.png
-
BINmail_all/static/description/2.png
-
BINmail_all/static/description/icon.png
-
84mail_all/static/description/index.html
-
3mail_all/static/src/css/mail_all.css
-
50mail_all/static/src/js/mail_all.js
-
33mail_all/static/src/js/test_mail_all.js
-
27mail_all/static/src/xml/menu.xml
-
3mail_all/tests/__init__.py
-
27mail_all/tests/test_js.py
-
21mail_all/views/templates.xml
-
25mail_archives/README.rst
-
0mail_archives/__init__.py
-
17mail_archives/__manifest__.py
-
11mail_archives/doc/changelog.rst
-
20mail_archives/doc/index.rst
-
36mail_archives/i18n/de.po
-
35mail_archives/i18n/es.po
-
30mail_archives/i18n/mail_archives.pot
-
35mail_archives/i18n/sl.po
-
BINmail_archives/images/1.jpg
-
BINmail_archives/static/description/1.png
-
BINmail_archives/static/description/2.png
-
BINmail_archives/static/description/icon.png
-
84mail_archives/static/description/index.html
-
3mail_archives/static/src/css/archives.css
-
91mail_archives/static/src/js/archives.js
-
24mail_archives/static/src/xml/menu.xml
-
1mail_archives/tests/__init__.py
-
31mail_archives/tests/test_js.py
-
21mail_archives/views/templates.xml
-
34mail_base/README.rst
-
2mail_base/__init__.py
-
16mail_base/__manifest__.py
-
1mail_base/controllers/__init__.py
-
15mail_base/controllers/main.py
-
22mail_base/doc/changelog.rst
-
18mail_base/doc/index.rst
-
31mail_base/i18n/de.po
-
29mail_base/i18n/es.po
-
29mail_base/i18n/it.po
-
29mail_base/i18n/pt.po
-
29mail_base/i18n/pt_BR.po
-
29mail_base/i18n/ru.po
-
31mail_base/i18n/sl.po
-
29mail_base/models.py
-
BINmail_base/static/description/icon.png
-
1418mail_base/static/lib/base.js
-
1mail_base/tests/__init__.py
-
17mail_base/tests/test_default.py
-
14mail_base/views/templates.xml
-
8mail_check_immediately/README.rst
-
1mail_check_immediately/__init__.py
-
15mail_check_immediately/__manifest__.py
-
4mail_check_immediately/doc/changelog.rst
-
80mail_check_immediately/models.py
-
BINmail_check_immediately/static/description/icon.png
-
49mail_check_immediately/static/description/index.html
-
BINmail_check_immediately/static/description/issue.png
-
BINmail_check_immediately/static/description/screenshot.png
-
60mail_check_immediately/static/src/js/main.js
-
29mail_check_immediately/static/src/xml/main.xml
-
16mail_check_immediately/views.xml
-
48mail_fix_553/README.rst
-
1mail_fix_553/__init__.py
@ -1 +1 @@ |
|||
new repo readme files |
|||
October 11, 2020 |
@ -1 +1,20 @@ |
|||
# see https://github.com/Tecnativa/doodba#optodoocustomsrcaddonsyaml |
|||
--- |
|||
ENV: |
|||
DEFAULT_REPO_PATTERN: https://github.com/OCA/{}.git |
|||
|
|||
web: |
|||
- "*" |
|||
|
|||
--- |
|||
ENV: |
|||
DEFAULT_REPO_PATTERN: https://github.com/itpp-labs/{}.git |
|||
|
|||
access-addons: |
|||
- "*" |
|||
|
|||
misc-addons: |
|||
- "*" |
|||
|
|||
website-addons: |
|||
- "*" |
@ -1,145 +0,0 @@ |
|||
# Copyright 2020 IT Projects Labs |
|||
# |
|||
# Licensed under the Apache License, Version 2.0 (the "License"); |
|||
# you may not use this file except in compliance with the License. |
|||
# You may obtain a copy of the License at |
|||
# |
|||
# http://www.apache.org/licenses/LICENSE-2.0 |
|||
# |
|||
# Unless required by applicable law or agreed to in writing, software |
|||
# distributed under the License is distributed on an "AS IS" BASIS, |
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
# See the License for the specific language governing permissions and |
|||
# limitations under the License. |
|||
name: "Port module" |
|||
|
|||
on: |
|||
issues: |
|||
types: |
|||
- opened |
|||
- reopened |
|||
|
|||
jobs: |
|||
port: |
|||
runs-on: ubuntu-latest |
|||
if: "startsWith(github.event.issue.title, 'DINAR-PORT ')" |
|||
steps: |
|||
- name: Post link |
|||
uses: KeisukeYamashita/create-comment@v1 |
|||
with: |
|||
comment: |
|||
"Porting is started. Check logs: https://github.com/${{ github.repository |
|||
}}/actions/runs/${{ github.run_id }}" |
|||
- name: Checkout DINAR |
|||
uses: actions/checkout@v2 |
|||
with: |
|||
path: DINAR |
|||
repository: itpp-labs/DINAR-fork |
|||
ref: master |
|||
- uses: actions/setup-python@v2 |
|||
with: |
|||
python-version: "3.7.x" |
|||
- name: Install python tools |
|||
run: | |
|||
pip install plumbum pre-commit git-aggregator |
|||
- name: Check Python Version |
|||
run: |
|||
echo "PY=$(python --version --version | sha256sum | cut -d' ' -f1)" >> |
|||
$GITHUB_ENV |
|||
- name: Analyze request |
|||
env: |
|||
TITLE: ${{ github.event.issue.title }} |
|||
run: | |
|||
# sets environment variables that available in next steps via $ {{ env.PORT_... }} notation |
|||
python DINAR/workflow-files/analyze_port_trigger.py "$TITLE" |
|||
- name: Checkout Repo |
|||
uses: actions/checkout@v2 |
|||
with: |
|||
path: REPO |
|||
fetch-depth: 0 |
|||
ref: ${{ env.PORT_TO_BRANCH }} |
|||
- uses: actions/cache@v1 |
|||
with: |
|||
path: ~/.cache/pre-commit |
|||
key: pre-commit|${{ env.PY }}|${{ hashFiles('REPO/.pre-commit-config.yaml') }} |
|||
- name: Copy module to new branch |
|||
run: | |
|||
git config --global user.email "itpp-bot@users.noreply.github.com" |
|||
git config --global user.name "Mitchell Admin" |
|||
cd REPO |
|||
if [ ! -d ${{ env.PORT_MODULE }} ] |
|||
then |
|||
# apply original commit history |
|||
if ! git format-patch --keep-subject --stdout origin/${{ env.PORT_TO_BRANCH }}..origin/${{ env.PORT_FROM_BRANCH }} -- ${{ env.PORT_MODULE }} | git am -3 --keep |
|||
then |
|||
# git am failed |
|||
git am --abort |
|||
|
|||
# just copy source |
|||
git checkout origin/${{ env.PORT_FROM_BRANCH }} -- ${{ env.PORT_MODULE }} |
|||
git commit -m ":tada:${{ env.PORT_FROM_BRANCH_TAGS }} ${{ env.PORT_MODULE }} |
|||
previous commits history: https://github.com/${{ github.repository }}/commits/${{ env.PORT_FROM_BRANCH }}/${{ env.PORT_MODULE }} |
|||
|
|||
> Made via .github/workflows/DINAR-PORT.yml" |
|||
fi |
|||
fi |
|||
- name: make OCA/odoo-module-migrator |
|||
run: | |
|||
gitaggregate -c DINAR/workflow-files/odoo-module-migrator-mix.yml |
|||
pip install -e ./odoo-module-migrator |
|||
- name: apply OCA/odoo-module-migrator |
|||
run: | |
|||
LOG_FILE=../odoo-module-migrator.logs |
|||
cd REPO |
|||
odoo-module-migrate \ |
|||
--modules ${{ env.PORT_MODULE }} \ |
|||
--init-version-name ${{ env.PORT_FROM_BRANCH }} \ |
|||
--target-version-name ${{ env.PORT_TO_BRANCH }} \ |
|||
--no-commit \ |
|||
--no-pre-commit \ |
|||
2> $LOG_FILE || true |
|||
cat $LOG_FILE |
|||
|
|||
# remove colors |
|||
sed -r -i "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g" $LOG_FILE |
|||
# escape character |
|||
# TODO: update KeisukeYamashita/create-comment to support reading comment's body from file |
|||
echo 'MIGRATOR_LOGS<<EOF' >> $GITHUB_ENV |
|||
cat $LOG_FILE >> $GITHUB_ENV |
|||
echo 'EOF' >> $GITHUB_ENV |
|||
|
|||
git add -A |
|||
git commit -m ":arrow_up:${{ env.PORT_TO_BRANCH_TAGS }} OCA/odoo-module-migrator |
|||
|
|||
close #${{ github.event.issue.number }} |
|||
|
|||
> Made via .github/workflows/DINAR-PORT.yml" |
|||
- name: pre-commit |
|||
run: | |
|||
cd REPO |
|||
pre-commit run --files $(find ${{ env.PORT_MODULE }} -type f) || true |
|||
git add -A |
|||
git commit -m ":rainbow: pre-commit |
|||
> Made via .github/workflows/DINAR-PORT.yml" || echo "pre-commit: no changes" |
|||
- name: PR |
|||
uses: peter-evans/create-pull-request@v3 |
|||
id: cpr |
|||
with: |
|||
path: REPO |
|||
# GITHUB_TOKEN would not trigger PR checks |
|||
token: ${{ secrets.DINAR_TOKEN }} |
|||
branch: ${{ env.PORT_TO_BRANCH }}-${{ env.PORT_MODULE }} |
|||
title: "[${{ env.PORT_TO_BRANCH }}] ${{ env.PORT_MODULE }}" |
|||
body: | |
|||
Made by [DINAR](https://github.com/itpp-labs/DINAR#readme) by request in #${{ github.event.issue.number }} |
|||
- name: Post logs |
|||
uses: KeisukeYamashita/create-comment@v1 |
|||
with: |
|||
number: ${{ steps.cpr.outputs.pull-request-number }} |
|||
comment: | |
|||
[Migrator](https://github.com/OCA/odoo-module-migrator/)'s [logs](https://github.com/${{ github.repository |
|||
}}/actions/runs/${{ github.run_id }}): |
|||
|
|||
``` |
|||
${{ env.MIGRATOR_LOGS }} |
|||
``` |
@ -0,0 +1,91 @@ |
|||
# Byte-compiled / optimized / DLL files |
|||
__pycache__/ |
|||
*.py[cod] |
|||
*$py.class |
|||
|
|||
# C extensions |
|||
*.so |
|||
|
|||
# Distribution / packaging |
|||
.Python |
|||
env/ |
|||
build/ |
|||
develop-eggs/ |
|||
dist/ |
|||
downloads/ |
|||
eggs/ |
|||
.eggs/ |
|||
lib/ |
|||
lib64/ |
|||
parts/ |
|||
sdist/ |
|||
var/ |
|||
*.egg-info/ |
|||
.installed.cfg |
|||
*.egg |
|||
*.pyc |
|||
*~ |
|||
|
|||
# PyInstaller |
|||
# Usually these files are written by a python script from a template |
|||
# before PyInstaller builds the exe, so as to inject date/other infos into it. |
|||
*.manifest |
|||
*.spec |
|||
|
|||
# Installer logs |
|||
pip-log.txt |
|||
pip-delete-this-directory.txt |
|||
|
|||
# Unit test / coverage reports |
|||
htmlcov/ |
|||
.tox/ |
|||
.coverage |
|||
.coverage.* |
|||
.cache |
|||
nosetests.xml |
|||
coverage.xml |
|||
*,cover |
|||
.hypothesis/ |
|||
|
|||
# Translations |
|||
*.mo |
|||
*.pot |
|||
|
|||
# Django stuff: |
|||
*.log |
|||
local_settings.py |
|||
|
|||
# Flask stuff: |
|||
instance/ |
|||
.webassets-cache |
|||
|
|||
# Scrapy stuff: |
|||
.scrapy |
|||
|
|||
# Sphinx documentation |
|||
docs/_build/ |
|||
|
|||
# PyBuilder |
|||
target/ |
|||
|
|||
# IPython Notebook |
|||
.ipynb_checkpoints |
|||
|
|||
# pyenv |
|||
.python-version |
|||
|
|||
# celery beat schedule file |
|||
celerybeat-schedule |
|||
|
|||
# dotenv |
|||
.env |
|||
|
|||
# virtualenv |
|||
venv/ |
|||
ENV/ |
|||
|
|||
# Spyder project settings |
|||
.spyderproject |
|||
|
|||
# Rope project settings |
|||
.ropeproject |
@ -0,0 +1,47 @@ |
|||
language: python |
|||
|
|||
python: |
|||
- "3.6" |
|||
|
|||
#dist: trusty |
|||
sudo: false |
|||
cache: pip |
|||
|
|||
addons: |
|||
# odoo 12.0+ uses command "INSERT ... ON CONFLICT ...", which is available in 9.5+ only |
|||
postgresql: "9.5" |
|||
apt: |
|||
packages: |
|||
- expect-dev # provides unbuffer utility |
|||
- python-lxml # because pip installation is slow |
|||
|
|||
env: |
|||
global: |
|||
- VERSION="13.0" TESTS="0" LINT_CHECK="0" UNIT_TEST="0" |
|||
- PYLINT_ODOO_JSLINTRC="/home/travis/maintainer-quality-tools/travis/cfg/.jslintrc" |
|||
|
|||
matrix: |
|||
- LINT_CHECK="1" |
|||
- CHECK_TAGS="1" |
|||
- TESTS="1" ODOO_REPO="odoo/odoo" MAKEPOT="1" |
|||
- TESTS="1" ODOO_REPO="OCA/OCB" |
|||
|
|||
install: |
|||
- pip install anybox.testing.openerp |
|||
- git clone https://github.com/it-projects-llc/maintainer-quality-tools.git |
|||
${HOME}/maintainer-quality-tools |
|||
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} |
|||
- travis_install_nightly |
|||
|
|||
script: |
|||
- travis_run_tests |
|||
|
|||
after_success: |
|||
- travis_after_tests_success |
|||
|
|||
notifications: |
|||
email: false |
|||
webhooks: |
|||
on_failure: change |
|||
urls: |
|||
- "https://ci.it-projects.info/travis/on_failure/change" |
@ -0,0 +1,23 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright 2020 IT-Projects Labs |
|||
Copyright 2015-2020 IT-Projects LLC |
|||
Copyright 2014-2015 Ivan Yelizariev |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
THE SOFTWARE. |
@ -0,0 +1 @@ |
|||
April 3, 2020 |
@ -0,0 +1,49 @@ |
|||
# Robust Mails |
|||
|
|||
This module is used to improve email deliverability and make sure that replies find |
|||
their way to the correct thread in Odoo. |
|||
|
|||
Options: |
|||
|
|||
- Force the `From` and `Reply-To` addresses of outgoing email |
|||
- Generate a thread-specific `Reply-To` address for outgoing emails so that losing the |
|||
headers used to identify the correct thread won't be a problem any more. |
|||
|
|||
## Gotcha |
|||
|
|||
To make the automatic bounce message work when using thread-specific `Reply-To` |
|||
addresses, you should define the actual catchall alias in a system parameter called |
|||
`mail.catchall.alias.custom` and change the `mail.catchall.alias` to something |
|||
completely random that will never be used, or alternatively remove it. |
|||
|
|||
The reason is this: when Odoo is looking for a route for an incoming email that has lost |
|||
its headers, it won't check whether the email was sent to `catchall@whatever.com` but |
|||
instead it will see if the local part of that address contains the word `catchall`. And |
|||
this isn't a good thing when the address is something like |
|||
`catchall+123abc@whatever.com`. That's why we had to skip the default catchall |
|||
evaluation and redo it in a later phase. |
|||
|
|||
## Database-specific Settings |
|||
|
|||
| Setting | Purpose | Default value | |
|||
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | |
|||
| email_headers.strip_mail_message_ids | Office 365 emails may add whitespaces before the Message-Id's. This feature removes them. | "True" | |
|||
| email_headers.prioritize_replyto_over_headers | When "True", Odoo will prioritize the (unique) Reply-To address of an incoming email and only then look at the `References` and `In-Reply-To` headers. | "True" | |
|||
| mail.catchall.alias | The default catchall alias. See "Gotcha" for more information. | "catchall" | |
|||
| mail.catchall.alias.custom | The new catchall alias setting. See "Gotcha" for more information. Will be set automatically upon module installation. | mail.catchall.alias value | |
|||
|
|||
## Debugging |
|||
|
|||
### Decode and decrypt a message id |
|||
|
|||
```python |
|||
from odoo.addons.email_headers.models.mail import decode_msg_id |
|||
decode_msg_id(<encrypted and base32/64 encoded message database id>, self.env) |
|||
``` |
|||
|
|||
### Encrypt and encode a message id |
|||
|
|||
```python |
|||
from odoo.addons.email_headers.models.mail import encode_msg_id |
|||
encode_msg_id(<message database id>, self.env) |
|||
``` |
@ -0,0 +1,32 @@ |
|||
############################################################################## |
|||
# |
|||
# Author: Avoin.Systems |
|||
# Copyright 2017 Avoin.Systems |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License as |
|||
# published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Affero General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################## |
|||
# noinspection PyUnresolvedReferences |
|||
from . import models |
|||
from odoo import SUPERUSER_ID, api |
|||
|
|||
|
|||
def set_catchall_alias(cr, registry): |
|||
env = api.Environment(cr, SUPERUSER_ID, {}) |
|||
icp = env["ir.config_parameter"] |
|||
custom_alias = icp.get_param("mail.catchall.alias.custom") |
|||
if not custom_alias: |
|||
original_alias = icp.get_param("mail.catchall.alias", "catchall") |
|||
icp.set_param("mail.catchall.alias.custom", original_alias) |
|||
icp.set_param("mail.catchall.alias", "Use mail.catchall.alias.custom") |
@ -0,0 +1,41 @@ |
|||
############################################################################## |
|||
# |
|||
# Author: Avoin.Systems |
|||
# Copyright 2017 Avoin.Systems |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License as |
|||
# published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Affero General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################## |
|||
|
|||
# noinspection PyStatementEffect |
|||
{ |
|||
"name": "Robust Mails", |
|||
"version": "13.0.1.2.0", |
|||
"license": "AGPL-3", |
|||
"summary": """ |
|||
Adds fields on outgoing email server that allows you to better control the |
|||
outgoing email headers and Reply-To addresses. |
|||
""", |
|||
"data": ["data/ir_config_parameter_data.xml", "views/ir_mail_server_views.xml"], |
|||
"author": "Avoin.Systems", |
|||
"website": "https://avoin.systems", |
|||
"category": "Email", |
|||
"depends": ["mail"], |
|||
"external_dependencies": { |
|||
"python": ["Crypto.Cipher.AES"], # pip3 install pycryptodome |
|||
"bin": [], |
|||
}, |
|||
"installable": True, |
|||
"post_init_hook": "set_catchall_alias", |
|||
} |
@ -0,0 +1,14 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<odoo noupdate="1"> |
|||
<record id="prioritize_replyto_over_headers" model="ir.config_parameter"> |
|||
<field name="key">email_headers.prioritize_replyto_over_headers</field> |
|||
<field name="value">True</field> |
|||
</record> |
|||
<!-- The original field was called just strip_message_ids, but since |
|||
it's manually set in some production databases, it's risky to |
|||
define here. It might break a database upgrade. --> |
|||
<record id="strip_mail_message_ids" model="ir.config_parameter"> |
|||
<field name="key">email_headers.strip_mail_message_ids</field> |
|||
<field name="value">True</field> |
|||
</record> |
|||
</odoo> |
@ -0,0 +1,9 @@ |
|||
def migrate(cr, version): |
|||
if not version: |
|||
return |
|||
|
|||
cr.execute( |
|||
"UPDATE ir_mail_server " |
|||
"SET reply_to_method = 'alias' " |
|||
"WHERE reply_to_alias IS TRUE" |
|||
) |
@ -0,0 +1,21 @@ |
|||
############################################################################## |
|||
# |
|||
# Author: Avoin.Systems |
|||
# Copyright 2018 Avoin.Systems |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License as |
|||
# published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Affero General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################## |
|||
# noinspection PyUnresolvedReferences |
|||
from . import mail |
@ -0,0 +1,585 @@ |
|||
############################################################################## |
|||
# |
|||
# Author: Avoin.Systems |
|||
# Copyright 2018 Avoin.Systems |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License as |
|||
# published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Affero General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################## |
|||
import base64 |
|||
import binascii |
|||
import logging |
|||
import random |
|||
import re |
|||
import string |
|||
from email.message import Message |
|||
from email.utils import formataddr, parseaddr |
|||
|
|||
from Crypto.Cipher import AES |
|||
|
|||
from odoo import api, fields, models, tools |
|||
from odoo.tools import frozendict |
|||
|
|||
from odoo.addons.base.models.ir_mail_server import encode_rfc2822_address_header |
|||
|
|||
_logger = logging.getLogger(__name__) |
|||
|
|||
|
|||
MESSAGE_PREFIX = "msg-" |
|||
|
|||
|
|||
def random_string(length): |
|||
return "".join( |
|||
random.choice(string.ascii_lowercase + string.digits) for _ in range(length) |
|||
) |
|||
|
|||
|
|||
def get_key(env): |
|||
return env["ir.config_parameter"].get_param("database.secret", "noneedtobestrong")[ |
|||
:16 |
|||
] |
|||
|
|||
|
|||
def get_cipher(env): |
|||
return AES.new( |
|||
get_key(env).encode("utf-8"), mode=AES.MODE_CBC, iv=b"veryverysecret81" |
|||
) |
|||
|
|||
|
|||
def encode_msg_id(msg_id, env): |
|||
id_padded = "%016d" % msg_id |
|||
encrypted = get_cipher(env).encrypt(id_padded.encode("utf-8")) |
|||
return base64.b32encode(encrypted).decode("utf-8") |
|||
|
|||
|
|||
# Remove in Odoo 14 |
|||
def encode_msg_id_legacy(msg_id, env): |
|||
id_padded = "%016d" % msg_id |
|||
encrypted = get_cipher(env).encrypt(id_padded.encode("utf-8")) |
|||
return base64.urlsafe_b64encode(encrypted).decode("utf-8") |
|||
|
|||
|
|||
def decode_msg_id(encoded_encrypted_id, env): |
|||
|
|||
try: |
|||
# Some email clients don't respect the original Reply-To address case |
|||
# and might make them lowercase. Make the encoded ID uppercase. |
|||
encrypted = base64.b32decode(encoded_encrypted_id.encode("utf-8").upper()) |
|||
except binascii.Error: |
|||
# Fall back to base64, which was used by the previous versions. |
|||
# This can be removed in Odoo 14. |
|||
try: |
|||
encrypted = base64.urlsafe_b64decode(encoded_encrypted_id.encode("utf-8")) |
|||
except binascii.Error: |
|||
_logger.error( |
|||
"Unable to decode the message ID. The input value " |
|||
"is invalid and cannot be decoded. " |
|||
"Encoded value: {}".format(encoded_encrypted_id) |
|||
) |
|||
raise |
|||
|
|||
try: |
|||
id_str = get_cipher(env).decrypt(encrypted).decode("utf-8") |
|||
except UnicodeDecodeError: |
|||
_logger.error( |
|||
"Unable to decrypt the message ID. The input value " |
|||
"probably wasn't encrypted with the same key. Encoded " |
|||
"value: {}".format(encoded_encrypted_id) |
|||
) |
|||
raise |
|||
|
|||
return int(id_str) |
|||
|
|||
|
|||
class MailServer(models.Model): |
|||
_inherit = "ir.mail_server" |
|||
|
|||
reply_to_method = fields.Selection( |
|||
[("default", "Odoo Default"), ("alias", "Alias"), ("msg_id", "Message ID")], |
|||
"Reply-To Method", |
|||
default="default", |
|||
help="Odoo Default: Don't add any unique identifiers into the\n" |
|||
"Reply-To address.\n" |
|||
"\n" |
|||
"Alias: Find or generate an email alias for the Reply-To field of\n " |
|||
"every outgoing message so the responses will be automatically \n" |
|||
"routed to the correct thread even if the email client (Yes, \n" |
|||
"I'm looking at you, Microsoft Outlook) decides to drop the \n" |
|||
"References, In-Reply-To and Message-ID fields.\n\n" |
|||
"The alias will then be used to generate a RFC 5233 sub-address\n" |
|||
"using the Force From Address field as a base, eg.\n" |
|||
"odoo@mycompany.fi would become odoo+adf9bacd98732@mycompany.fi\n" |
|||
"\n" |
|||
"Note that this method has a flaw: if the headers have dropped\n" |
|||
"and Odoo can't connect the reply to any message in the thread,\n" |
|||
"it will automatically connect it to the first message in the \n" |
|||
"thread which often is an internal note and the reply will also\n" |
|||
"be marked as an internal note even when it should be a comment." |
|||
"\n\n" |
|||
"Message ID: Include a prefix and the message ID in encrypted\n" |
|||
"and base32 encoded format in the Reply-To\n" |
|||
"address to that Odoo will be able to directly connect the\n" |
|||
"reply to the original message. Note that in this mode the\n" |
|||
"Reply-To address has a priority over References and\n" |
|||
"In-Reply-To headers.", |
|||
) |
|||
|
|||
force_email_reply_to = fields.Char("Force Reply-To Address",) |
|||
|
|||
force_email_reply_to_name = fields.Char("Force Reply-To Name",) |
|||
|
|||
force_email_reply_to_domain = fields.Char("Force Reply-To Domain",) |
|||
|
|||
force_email_from = fields.Char("Force From Address",) |
|||
|
|||
force_email_sender = fields.Char("Force Sender Address",) |
|||
|
|||
prioritize_reply_to_over_msgid = fields.Boolean( |
|||
"Prioritize Reply-To Over Email Headers", |
|||
default=True, |
|||
help="If this field is selected, the unique Reply-To address " |
|||
"generated by the Message ID method will be prioritized " |
|||
"over the email headers (default Odoo behavior) in incoming " |
|||
"emails. This is recommended when the Reply-To method is set to " |
|||
"Message ID.", |
|||
) |
|||
|
|||
headers_example = fields.Text( |
|||
"Example Headers", compute="_compute_headers_example", store=False, |
|||
) |
|||
|
|||
# TODO Implement field input validators |
|||
def _get_reply_to_address(self, alias, original_from_name): |
|||
self.ensure_one() |
|||
|
|||
force_email_from = encode_rfc2822_address_header(self.force_email_from) |
|||
|
|||
# Split the From address |
|||
from_address = force_email_from.split("@") |
|||
|
|||
reply_to_addr = "{alias}@{domain}".format( |
|||
alias=alias if alias else from_address[0], |
|||
domain=self.force_email_reply_to_domain or from_address[1], |
|||
) |
|||
|
|||
if self.force_email_reply_to_name: |
|||
reply_to = formataddr((self.force_email_reply_to_name, reply_to_addr)) |
|||
elif original_from_name: |
|||
reply_to = formataddr((original_from_name, reply_to_addr)) |
|||
else: |
|||
reply_to = reply_to_addr |
|||
|
|||
return encode_rfc2822_address_header(reply_to) |
|||
|
|||
@api.depends( |
|||
"force_email_sender", |
|||
"force_email_reply_to", |
|||
"force_email_reply_to_domain", |
|||
"force_email_from", |
|||
"force_email_reply_to_name", |
|||
"reply_to_method", |
|||
) |
|||
def _compute_headers_example(self): |
|||
for server in self: |
|||
example = [] |
|||
if server.force_email_sender: |
|||
example.append("Sender: {}".format(server.force_email_sender)) |
|||
|
|||
if server.force_email_reply_to: |
|||
example.append("Reply-To: {}".format(server.force_email_reply_to)) |
|||
elif server.force_email_from and server.reply_to_method != "default": |
|||
reply_to_pair = server.force_email_from.split("@") |
|||
|
|||
if server.reply_to_method == "alias": |
|||
token = "{}+1d278g1082bca" |
|||
elif server.reply_to_method == "msg_id": |
|||
token = "{}+" + MESSAGE_PREFIX + "p2IxKkfEKugl16juheTT0g==" |
|||
else: |
|||
token = "INVALID" |
|||
_logger.error( |
|||
"Invalid reply_to_method found: " + server.reply_to_method |
|||
) |
|||
|
|||
# noinspection PyProtectedMember |
|||
reply_to = server._get_reply_to_address( |
|||
token.format(reply_to_pair[0]), "Original From Person" |
|||
) |
|||
example.append("Reply-To: {}".format(reply_to)) |
|||
else: |
|||
example.append("Reply-To: Odoo default") |
|||
|
|||
if server.force_email_from: |
|||
example.append( |
|||
"From: {}".format( |
|||
formataddr(("Original From Person", server.force_email_from)) |
|||
) |
|||
) |
|||
else: |
|||
example.append("From: Odoo default") |
|||
|
|||
server.headers_example = "\n".join(example) |
|||
|
|||
@api.model |
|||
def send_email( |
|||
self, |
|||
message, |
|||
mail_server_id=None, |
|||
smtp_server=None, |
|||
smtp_port=None, |
|||
smtp_user=None, |
|||
smtp_password=None, |
|||
smtp_encryption=None, |
|||
smtp_debug=False, |
|||
smtp_session=None, |
|||
): |
|||
|
|||
# Get SMTP Server Details from Mail Server |
|||
mail_server = None |
|||
if mail_server_id: |
|||
mail_server = self.sudo().browse(mail_server_id) |
|||
elif not smtp_server: |
|||
mail_server = self.sudo().search([], order="sequence", limit=1) |
|||
|
|||
# Note that Odoo already has the ability to use a fixed From address |
|||
# by settings "email_from" in the Odoo settings. This is however a |
|||
# secondary option and here email_from always overrides that. |
|||
if mail_server.force_email_from: |
|||
original_from_name = parseaddr(message["From"])[0] |
|||
force_email_from = encode_rfc2822_address_header( |
|||
mail_server.force_email_from |
|||
) |
|||
del message["From"] |
|||
message["From"] = formataddr((original_from_name, force_email_from)) |
|||
|
|||
if mail_server.reply_to_method == "alias": |
|||
# Find or create an email alias |
|||
alias = self.find_or_create_alias(force_email_from.split("@")) |
|||
# noinspection PyProtectedMember |
|||
reply_to = mail_server._get_reply_to_address(alias, original_from_name,) |
|||
del message["Reply-To"] |
|||
message["Reply-To"] = reply_to |
|||
|
|||
elif mail_server.reply_to_method == "msg_id": |
|||
odoo_msg_id = message.get("Message-Id") |
|||
if odoo_msg_id: |
|||
# The message_id isn't unique. Prefer the one that has a |
|||
# model set and only pick the first record. Odoo does |
|||
# almost the same thing in mail.thread.message_route(). |
|||
odoo_msg = ( |
|||
self.sudo() |
|||
.env["mail.message"] |
|||
.search( |
|||
[("message_id", "=", odoo_msg_id)], order="model", limit=1 |
|||
) |
|||
) |
|||
|
|||
encrypted_id = encode_msg_id(odoo_msg.id, self.env) |
|||
# noinspection PyProtectedMember |
|||
reply_to = mail_server._get_reply_to_address( |
|||
"{}+{}{}".format( |
|||
force_email_from.split("@")[0], MESSAGE_PREFIX, encrypted_id |
|||
), |
|||
original_from_name, |
|||
) |
|||
|
|||
_logger.info( |
|||
'Generated a new reply-to address "{}" for ' |
|||
'Message-Id "{}".'.format(reply_to, odoo_msg_id) |
|||
) |
|||
|
|||
del message["Reply-To"] |
|||
message["Reply-To"] = reply_to |
|||
else: |
|||
_logger.warning( |
|||
"Couldn't get Message-Id from the message {}. The " |
|||
"reply might not find its way to the correct thread.".format( |
|||
message.as_string() |
|||
) |
|||
) |
|||
|
|||
if mail_server.force_email_reply_to: |
|||
del message["Reply-To"] |
|||
message["Reply-To"] = encode_rfc2822_address_header( |
|||
mail_server.force_email_reply_to |
|||
) |
|||
|
|||
if mail_server.force_email_sender: |
|||
del message["Sender"] |
|||
message["Sender"] = encode_rfc2822_address_header( |
|||
mail_server.force_email_sender |
|||
) |
|||
|
|||
return super(MailServer, self).send_email( |
|||
message, |
|||
mail_server_id, |
|||
smtp_server, |
|||
smtp_port, |
|||
smtp_user, |
|||
smtp_password, |
|||
smtp_encryption, |
|||
smtp_debug, |
|||
smtp_session, |
|||
) |
|||
|
|||
def find_or_create_alias(self, from_address): |
|||
|
|||
record_id, record_model_name = self.resolve_record() |
|||
if not record_id or not record_model_name: |
|||
# Can't create an alias if we don't know the related record |
|||
return False |
|||
|
|||
if record_model_name not in self.env: |
|||
_logger.error( |
|||
"Unable to find or create an alias for outgoing " |
|||
"email: invalid_model name {}.".format(record_model_name) |
|||
) |
|||
return False |
|||
|
|||
# Find an alias |
|||
alias_model_id = ( |
|||
self.env["ir.model"].search([("model", "=", record_model_name)]).id |
|||
) |
|||
# noinspection PyPep8Naming |
|||
Alias = self.env["mail.alias"] |
|||
existing_aliases = Alias.search( |
|||
[ |
|||
("alias_model_id", "=", alias_model_id), |
|||
( |
|||
"alias_name", |
|||
"like", |
|||
"{from_address}+".format(from_address=from_address[0]), |
|||
), |
|||
("alias_force_thread_id", "=", record_id), |
|||
("alias_contact", "=", "everyone"), # TODO: check from record |
|||
] |
|||
) |
|||
|
|||
if existing_aliases: |
|||
return existing_aliases[0].alias_name |
|||
|
|||
# Create a new alias |
|||
alias = Alias.create( |
|||
{ |
|||
"alias_model_id": alias_model_id, |
|||
"alias_name": "{from_address}+{random_string}".format( |
|||
from_address=from_address[0], random_string=random_string(8) |
|||
), |
|||
"alias_force_thread_id": record_id, |
|||
"alias_contact": "everyone", |
|||
} |
|||
) |
|||
|
|||
return alias.alias_name |
|||
|
|||
def resolve_record(self): |
|||
ctx = self.env.context |
|||
# Don't ever use active_id or active_model from the context here. |
|||
# It might not be the one that you expect. Go ahead and try, open |
|||
# a sales order, go to the related purchase order and send the PO. |
|||
record_id = ctx.get("default_res_id") |
|||
record_model_name = ctx.get("default_model") |
|||
|
|||
# If incoming_routes isn't enough, we can use ctx['incoming_to'] to |
|||
# find a alias directly without active_id and active_model_name. |
|||
routes = ctx.get("incoming_routes", []) |
|||
if (not record_id or not record_model_name) and routes and len(routes) > 0: |
|||
route = routes[0] |
|||
record_model_name = route[0] |
|||
record_id = route[1] |
|||
|
|||
return record_id, record_model_name |
|||
|
|||
@api.model |
|||
def encrypt_message_id(self, message_id): |
|||
""" |
|||
A helper encryption method for debugging mail delivery issues. |
|||
:param message_id: The id of the `mail.message` |
|||
:return: The id of the `mail.message` encrypted and base64 encoded |
|||
""" |
|||
return encode_msg_id(message_id, self.env) |
|||
|
|||
@api.model |
|||
def decrypt_message_id(self, encrypted_id): |
|||
""" |
|||
A helper decryption method for debugging mail delivery issues. |
|||
:param encrypted_id: The encrypted and base64 encoded id of |
|||
the `mail.message` to be decrypted |
|||
:return: The id of the `mail.message` |
|||
""" |
|||
return decode_msg_id(encrypted_id, self.env) |
|||
|
|||
|
|||
class MailThread(models.AbstractModel): |
|||
|
|||
_inherit = "mail.thread" |
|||
|
|||
""" |
|||
The process for incoming emails goes something like this: |
|||
1. message_process (processing the incoming message) |
|||
2. message_parse (parsing the email message) |
|||
3. message_route (decides how to route the email) |
|||
4. message_route_process (executes the route) |
|||
5. message_post (posts the message to a thread) |
|||
""" |
|||
|
|||
@api.model |
|||
def message_parse(self, message, save_original=False): |
|||
email_to = tools.decode_message_header(message, "To") |
|||
email_to_localpart = (tools.email_split(email_to) or [""])[0].split("@", 1)[0] |
|||
|
|||
config_params = self.env["ir.config_parameter"].sudo() |
|||
|
|||
# Check if the To part contains the prefix and a base32/64 encoded string |
|||
# Remove the "24," part when migrating to Odoo 14. |
|||
prefix_in_to = email_to_localpart and re.search( |
|||
r".*" + MESSAGE_PREFIX + "(?P<odoo_id>.{24,32}$)", email_to_localpart |
|||
) |
|||
|
|||
prioritize_replyto_over_headers = config_params.get_param( |
|||
"email_headers.prioritize_replyto_over_headers", "True" |
|||
) |
|||
prioritize_replyto_over_headers = ( |
|||
True if prioritize_replyto_over_headers != "False" else False |
|||
) |
|||
|
|||
# If the msg prefix part is found in the To part, find the parent |
|||
# message and inject the Message-Id to the In-Reply-To part and |
|||
# remove References because it by default takes priority over |
|||
# In-Reply-To. We want the unique Reply-To address have the priority. |
|||
if prefix_in_to and prioritize_replyto_over_headers: |
|||
message_id_encrypted = prefix_in_to.group("odoo_id") |
|||
try: |
|||
message_id = decode_msg_id(message_id_encrypted, self.env) |
|||
parent_id = self.env["mail.message"].browse(message_id) |
|||
if parent_id: |
|||
# See unit test test_reply_to_method_msg_id_priority |
|||
del message["References"] |
|||
del message["In-Reply-To"] |
|||
message["In-Reply-To"] = parent_id.message_id |
|||
else: |
|||
_logger.warning( |
|||
"Received an invalid mail.message database id in incoming " |
|||
"email sent to {}. The email type (comment, note) might " |
|||
"be wrong.".format(email_to) |
|||
) |
|||
except UnicodeDecodeError: |
|||
_logger.warning( |
|||
"Unique Reply-To address of an incoming email couldn't be " |
|||
"decrypted. Falling back to default Odoo behavior." |
|||
) |
|||
|
|||
res = super(MailThread, self).message_parse(message, save_original) |
|||
|
|||
strip_message_id = config_params.get_param( |
|||
"email_headers.strip_mail_message_ids", "True" |
|||
) |
|||
strip_message_id = True if strip_message_id != "False" else False |
|||
|
|||
if not strip_message_id == "True": |
|||
return res |
|||
|
|||
# When Odoo compares message_id to the one stored in the database when determining |
|||
# whether or not the incoming message is a reply to another one, the message_id search |
|||
# parameter is stripped before the search. But Odoo does not do anything of the sort when |
|||
# a message is created, meaning if some email software (for example Outlook, |
|||
# for no particular reason) includes anything strippable at the start of the Message-Id, |
|||
# any replies to that message in the future will not find their way correctly, as the |
|||
# search yields nothing. |
|||
# |
|||
# Example of what happened before. The first one is the original Message-Id, and thus also |
|||
# the ID that gets stored on the mail.message as the `message_id` |
|||
# '\r\n <AM6PR05MB4933DE6BCAD68A037185EBCFFBAF0@AM6PR05MB4933.eurprd05.prod.outlook.com>' |
|||
# But when trying to find this message, Odoo takes the above message_id and strips it, |
|||
# which results in: |
|||
# '<AM6PR05MB4933DE6BCAD68A037185EBCFFBAF0@AM6PR05MB4933.eurprd05.prod.outlook.com>' |
|||
# And then the search is done for an exact match, which will fail. |
|||
# |
|||
# Odoo doesn't, so we must strip the message_ids before they are stored in the database |
|||
mail_message_id = res.get("message_id", "") |
|||
if mail_message_id: |
|||
mail_message_id = mail_message_id.strip() |
|||
res["message_id"] = mail_message_id |
|||
return res |
|||
|
|||
@api.model |
|||
def message_route_process(self, message, message_dict, routes): |
|||
ctx = self.env.context.copy() |
|||
ctx["incoming_routes"] = routes |
|||
ctx["incoming_to"] = message_dict.get("to") |
|||
self.env.context = frozendict(ctx) |
|||
return super(MailThread, self).message_route_process( |
|||
message, message_dict, routes |
|||
) |
|||
|
|||
@api.model |
|||
def message_route( |
|||
self, message, message_dict, model=None, thread_id=None, custom_values=None |
|||
): |
|||
|
|||
# NOTE! If you're going to backport this module to Odoo 11 or Odoo 10, |
|||
# you will have to create the mail_bounce_catchall email template |
|||
# because it was introduced only in Odoo 12. |
|||
|
|||
if not isinstance(message, Message): |
|||
raise TypeError("message must be an " "email.message.Message at this point") |
|||
|
|||
try: |
|||
route = super(MailThread, self).message_route( |
|||
message, message_dict, model, thread_id, custom_values |
|||
) |
|||
except ValueError: |
|||
|
|||
# If the headers that connect the incoming message to a thread in |
|||
# Odoo have disappeared at some point and the message was sent to |
|||
# the catchall address (with a sub-addressing suffix), we will |
|||
# skip the default catchall check and perform it here for |
|||
# mail.catchall.alias.custom. We do this because the alias check |
|||
# if done AFTER the catchall check by default and it may cause |
|||
# Odoo to send a bounce message to the sender who sent the email to |
|||
# the correct thread-specific address. |
|||
|
|||
catchall_alias = ( |
|||
self.env["ir.config_parameter"] |
|||
.sudo() |
|||
.get_param("mail.catchall.alias.custom") |
|||
) |
|||
|
|||
email_to = tools.decode_message_header(message, "To") |
|||
email_to_localpart = ( |
|||
(tools.email_split(email_to) or [""])[0].split("@", 1)[0].lower() |
|||
) |
|||
|
|||
message_id = message.get("Message-Id") |
|||
email_from = tools.decode_message_header(message, "From") |
|||
|
|||
# check it does not directly contact catchall |
|||
if catchall_alias and catchall_alias in email_to_localpart: |
|||
_logger.info( |
|||
"Routing mail from %s to %s with Message-Id %s: " |
|||
"direct write to catchall, bounce", |
|||
email_from, |
|||
email_to, |
|||
message_id, |
|||
) |
|||
body = self.env.ref("mail.mail_bounce_catchall").render( |
|||
{"message": message}, engine="ir.qweb" |
|||
) |
|||
self._routing_create_bounce_email( |
|||
email_from, body, message, reply_to=self.env.user.company_id.email |
|||
) |
|||
return [] |
|||
else: |
|||
raise |
|||
|
|||
return route |
@ -0,0 +1 @@ |
|||
from . import test_email |
@ -0,0 +1,353 @@ |
|||
# No need to translate tests |
|||
# pylint: disable=translation-required |
|||
from email.message import EmailMessage |
|||
|
|||
import mock |
|||
|
|||
import odoo |
|||
from odoo import SUPERUSER_ID |
|||
from odoo.tests import TransactionCase |
|||
|
|||
from odoo.addons.base.models.ir_mail_server import IrMailServer |
|||
|
|||
from ..models.mail import ( |
|||
MESSAGE_PREFIX, |
|||
encode_msg_id, |
|||
encode_msg_id_legacy, |
|||
random_string, |
|||
) |
|||
|
|||
|
|||
@odoo.tests.tagged("post_install", "-at_install") |
|||
class TestEmail(TransactionCase): |
|||
def setUp(self): |
|||
super(TestEmail, self).setUp() |
|||
|
|||
self.partner = self.env["res.partner"].create({"name": "Test Dude"}) |
|||
self.partner2 = self.env["res.partner"].create({"name": "Dudette"}) |
|||
self.demo_user = self.env.ref("base.user_demo") |
|||
self.subtype_comment = self.env.ref("mail.mt_comment") |
|||
self.subtype_note = self.env.ref("mail.mt_note") |
|||
|
|||
self.MailMessage = self.env["mail.message"] |
|||
self.ConfigParam = self.env["ir.config_parameter"] |
|||
|
|||
# Create server configuration |
|||
self.outgoing_server = self.env["ir.mail_server"].create( |
|||
{ |
|||
"name": "Outgoing SMTP Server for Unit Tests", |
|||
"sequence": 1, |
|||
"smtp_host": "localhost", |
|||
"smtp_port": "9999", |
|||
"smtp_encryption": "none", |
|||
"smtp_user": "doesnt", |
|||
"smtp_pass": "exist", |
|||
"reply_to_method": "msg_id", |
|||
"force_email_reply_to_domain": "example.com", |
|||
"force_email_from": "odoo@example.com", |
|||
} |
|||
) |
|||
|
|||
@staticmethod |
|||
def create_email_message(): |
|||
message = EmailMessage() |
|||
message[ |
|||
"Content-Type" |
|||
] = 'multipart/mixed; boundary="===============2590914155756834027=="' |
|||
message["MIME-Version"] = "1.0" |
|||
message[ |
|||
"Message-Id" |
|||
] = "<CAD-eYi=a264_3DcrYSDU5yc_fwYoHonZ3H+{}@mail.gmail.com>".format( |
|||
random_string(6) |
|||
) |
|||
message["Subject"] = "1" |
|||
message["From"] = "Miku Laitinen <miku@avoin.systems>" |
|||
message["Reply-To"] = "YourCompany Eurooppa <sales@avoin.onmicrosoft.com>" |
|||
message["To"] = '"Erik N. French" <ErikNFrench@armyspy.com>' |
|||
message["Date"] = "Mon, 06 May 2019 14:16:38 -0000" |
|||
return message |
|||
|
|||
def test_reply_to_method_msg_id(self): |
|||
|
|||
# Make administrator follow the partner |
|||
self.partner.message_subscribe([self.env.user.partner_id.id]) |
|||
|
|||
# Send a message to the followers of the partner |
|||
thread_msg = self.partner.with_user(self.demo_user).message_post( |
|||
body="dummy message.", message_type="comment", subtype="mail.mt_comment" |
|||
) |
|||
|
|||
# Make sure the message headers look right.. or not |
|||
# mail_msg = thread_msg.notification_ids[0] |
|||
|
|||
# Get the encoded message address |
|||
encoded_msg_id = encode_msg_id(thread_msg.id, self.env) |
|||
|
|||
# Try to read an incoming email |
|||
message = self.create_email_message() |
|||
del message["To"] |
|||
message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format( |
|||
MESSAGE_PREFIX, encoded_msg_id |
|||
) |
|||
|
|||
thread_id = self.env["mail.thread"].message_process( |
|||
model=False, message=message.as_string() |
|||
) |
|||
self.assertEqual( |
|||
thread_msg.res_id, |
|||
thread_id, |
|||
"The incoming email wasn't connected to the correct thread", |
|||
) |
|||
|
|||
# Make sure the message is a comment |
|||
incoming_msg1 = self.MailMessage.search( |
|||
[("message_id", "=", message["Message-Id"])] |
|||
) |
|||
self.assertEqual( |
|||
incoming_msg1.message_type, |
|||
"email", |
|||
"The incoming message was created as a type {} instead of a email.".format( |
|||
incoming_msg1.message_type |
|||
), |
|||
) |
|||
self.assertEqual( |
|||
incoming_msg1.subtype_id, |
|||
self.subtype_comment, |
|||
"The incoming message was created as a subtype {} instead of a comment.".format( |
|||
incoming_msg1.subtype_id |
|||
), |
|||
) |
|||
|
|||
# Try to read another incoming email |
|||
message = self.create_email_message() |
|||
del message["To"] |
|||
message["To"] = '"Erik N. French" <ErikNFrench+{}HURDURLUR@armyspy.com>'.format( |
|||
MESSAGE_PREFIX |
|||
) |
|||
message["In-Reply-To"] = thread_msg.message_id |
|||
|
|||
thread_id = self.env["mail.thread"].message_process( |
|||
model=False, message=message.as_string() |
|||
) |
|||
self.assertEqual( |
|||
thread_msg.res_id, |
|||
thread_id, |
|||
"The incoming email wasn't connected to the correct thread", |
|||
) |
|||
|
|||
# Make sure the message is a comment |
|||
incoming_msg2 = self.MailMessage.search( |
|||
[("message_id", "=", message["Message-Id"])] |
|||
) |
|||
self.assertEqual( |
|||
incoming_msg2.message_type, |
|||
"email", |
|||
"The incoming message was created as a type {} instead of a email.".format( |
|||
incoming_msg2.message_type |
|||
), |
|||
) |
|||
self.assertEqual( |
|||
incoming_msg2.subtype_id, |
|||
self.subtype_comment, |
|||
"The incoming message was created as a subtype {} instead of a comment.".format( |
|||
incoming_msg2.subtype_id |
|||
), |
|||
) |
|||
|
|||
def test_reply_to_method_msg_id_priority(self): |
|||
""" |
|||
In this test we will inject the wrong Message-Id to the incoming |
|||
email messages References-header and see if Odoo will prioritize |
|||
the custom Reply-To address over the References-header. It should. |
|||
:return: |
|||
""" |
|||
|
|||
# Make administrator follow the partner |
|||
self.partner.message_subscribe([self.env.user.partner_id.id]) |
|||
|
|||
# Send a message to the followers of the partner |
|||
thread_msg = self.partner.with_user(self.demo_user).message_post( |
|||
body="dummy message X.", message_type="comment", subtype="mail.mt_comment" |
|||
) |
|||
|
|||
# Get the encoded message address |
|||
encoded_msg_id = encode_msg_id(thread_msg.id, self.env) |
|||
|
|||
# Send another message to the followers of the partner |
|||
thread_msg2 = self.partner2.with_user(self.demo_user).message_post( |
|||
body="dummy message X.", message_type="comment", subtype="mail.mt_comment" |
|||
) |
|||
|
|||
# Try to read an incoming email |
|||
message = self.create_email_message() |
|||
del message["To"] |
|||
del message["References"] |
|||
message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format( |
|||
MESSAGE_PREFIX, encoded_msg_id |
|||
) |
|||
|
|||
# Inject the wrong References |
|||
message["References"] = thread_msg2.message_id |
|||
|
|||
thread_id = self.env["mail.thread"].message_process( |
|||
model=False, message=message.as_string() |
|||
) |
|||
self.assertEqual( |
|||
thread_msg.res_id, |
|||
thread_id, |
|||
"The incoming email wasn't connected to the correct thread", |
|||
) |
|||
|
|||
def test_reply_to_method_msg_id_notification(self): |
|||
|
|||
# Make administrator follow the partner |
|||
self.partner2.message_subscribe([self.env.user.partner_id.id]) |
|||
|
|||
# Send a message to the followers of the partner |
|||
thread_msg = self.partner2.with_user(self.demo_user).message_post( |
|||
body="dummy message 2.", message_type="comment", subtype="mail.mt_note" |
|||
) |
|||
|
|||
# Get the encoded message address |
|||
encoded_msg_id = encode_msg_id(thread_msg.id, self.env) |
|||
|
|||
# Try to read an incoming email |
|||
message = self.create_email_message() |
|||
del message["To"] |
|||
message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format( |
|||
MESSAGE_PREFIX, encoded_msg_id |
|||
) |
|||
|
|||
thread_id = self.env["mail.thread"].message_process( |
|||
model=False, message=message.as_string() |
|||
) |
|||
self.assertEqual( |
|||
thread_msg.res_id, |
|||
thread_id, |
|||
"The incoming email wasn't connected to the correct thread", |
|||
) |
|||
|
|||
# Make sure the message is a note |
|||
incoming_msg1 = self.MailMessage.search( |
|||
[("message_id", "=", message["Message-Id"])] |
|||
) |
|||
self.assertEqual( |
|||
incoming_msg1.message_type, |
|||
"email", |
|||
"The incoming message was created as a type {} instead of a email.".format( |
|||
incoming_msg1.message_type |
|||
), |
|||
) |
|||
self.assertEqual( |
|||
incoming_msg1.subtype_id, |
|||
self.subtype_note, |
|||
"The incoming message was created as a subtype {} instead of a note.".format( |
|||
incoming_msg1.subtype_id |
|||
), |
|||
) |
|||
|
|||
def test_reply_to_method_msg_id_legacy(self): |
|||
# REMOVE this test when porting to Odoo 14 |
|||
|
|||
# Make administrator follow the partner |
|||
self.partner2.message_subscribe([self.env.user.partner_id.id]) |
|||
|
|||
# Send a message to the followers of the partner |
|||
thread_msg = self.partner2.with_user(self.demo_user).message_post( |
|||
body="dummy message 2.", message_type="comment", subtype="mail.mt_note" |
|||
) |
|||
|
|||
# Get the encoded message address |
|||
encoded_msg_id = encode_msg_id_legacy(thread_msg.id, self.env) |
|||
|
|||
# Try to read an incoming email |
|||
message = self.create_email_message() |
|||
del message["To"] |
|||
message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format( |
|||
MESSAGE_PREFIX, encoded_msg_id |
|||
) |
|||
|
|||
thread_id = self.env["mail.thread"].message_process( |
|||
model=False, message=message.as_string() |
|||
) |
|||
self.assertEqual( |
|||
thread_msg.res_id, |
|||
thread_id, |
|||
"The incoming email wasn't connected to the correct thread", |
|||
) |
|||
|
|||
def test_reply_to_method_msg_id_lowercase(self): |
|||
# Make administrator follow the partner |
|||
self.partner2.message_subscribe([self.env.user.partner_id.id]) |
|||
|
|||
# Send a message to the followers of the partner |
|||
thread_msg = self.partner2.with_user(self.demo_user).message_post( |
|||
body="dummy message 2.", message_type="comment", subtype="mail.mt_note" |
|||
) |
|||
|
|||
# Get the encoded message address |
|||
encoded_msg_id = encode_msg_id(thread_msg.id, self.env).lower() |
|||
|
|||
# Try to read an incoming email |
|||
message = self.create_email_message() |
|||
del message["To"] |
|||
message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format( |
|||
MESSAGE_PREFIX, encoded_msg_id |
|||
) |
|||
|
|||
thread_id = self.env["mail.thread"].message_process( |
|||
model=False, message=message.as_string() |
|||
) |
|||
self.assertEqual( |
|||
thread_msg.res_id, |
|||
thread_id, |
|||
"The incoming email wasn't connected to the correct thread", |
|||
) |
|||
|
|||
def test_outgoing_msg_id(self): |
|||
# Make administrator follow the partner |
|||
self.partner2.message_subscribe([SUPERUSER_ID]) |
|||
|
|||
with mock.patch.object(IrMailServer, "send_email") as send_email: |
|||
# Send a message to the followers of the partner |
|||
thread_msg = self.partner2.with_user(self.demo_user).message_post( |
|||
body="dummy message 3.", |
|||
message_type="comment", |
|||
subtype="mail.mt_comment", |
|||
) |
|||
|
|||
# Get the encoded message address |
|||
encoded_msg_id = encode_msg_id(thread_msg.id, self.env) |
|||
|
|||
self.assertTrue( |
|||
send_email.called, |
|||
"IrMailServer.send_email wasn't called when sending outgoing email", |
|||
) |
|||
|
|||
message = send_email.call_args[0][0] |
|||
|
|||
reply_to_address = "{}{}@{}".format( |
|||
MESSAGE_PREFIX, |
|||
encoded_msg_id, |
|||
self.outgoing_server.force_email_reply_to_domain, |
|||
) |
|||
|
|||
# Make sure the subaddress is correct in the Reply-To field |
|||
self.assertIn( |
|||
reply_to_address, |
|||
message["Reply-To"], |
|||
"Reply-To address didn't contain the correct subaddress", |
|||
) |
|||
|
|||
# Make sure the author name is in the Reply-To field |
|||
self.assertIn( |
|||
thread_msg.author_id.name, |
|||
message["Reply-To"], |
|||
"Reply-To address didn't contain the author name", |
|||
) |
|||
|
|||
self.assertIn( |
|||
self.outgoing_server.force_email_from, |
|||
message["From"], |
|||
"From address didn't contain the configure From-address", |
|||
) |
@ -0,0 +1,22 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<odoo> |
|||
<!-- FORM: ir.mail.server --> |
|||
<record id="ir_mail_server_form_email_headers" model="ir.ui.view"> |
|||
<field name="name">ir.mail_server.form.email.headers</field> |
|||
<field name="model">ir.mail_server</field> |
|||
<field name="inherit_id" ref="base.ir_mail_server_form" /> |
|||
<field name="arch" type="xml"> |
|||
<xpath expr="//group[last()]" position="after"> |
|||
<group name="advanced" string="Smart Headers"> |
|||
<field name="force_email_sender" /> |
|||
<field name="force_email_reply_to" /> |
|||
<field name="force_email_reply_to_name" /> |
|||
<field name="force_email_reply_to_domain" /> |
|||
<field name="force_email_from" /> |
|||
<field name="reply_to_method" /> |
|||
<field name="headers_example" /> |
|||
</group> |
|||
</xpath> |
|||
</field> |
|||
</record> |
|||
</odoo> |
@ -0,0 +1,29 @@ |
|||
.. image:: https://itpp.dev/images/infinity-readme.png |
|||
:alt: Tested and maintained by IT Projects Labs |
|||
:target: https://itpp.dev |
|||
|
|||
.. image:: https://img.shields.io/badge/license-MIT-blue.svg |
|||
:target: https://opensource.org/licenses/MIT |
|||
:alt: License: MIT |
|||
|
|||
=================== |
|||
Show all messages |
|||
=================== |
|||
|
|||
Adds ``Discuss / All`` menu, that shows all messages accessible by current user. |
|||
|
|||
Questions? |
|||
========== |
|||
|
|||
To get an assistance on this module contact us by email :arrow_right: help@itpp.dev |
|||
|
|||
Contributors |
|||
============ |
|||
* Pavel Romanchenko <apps@it-projects.info> |
|||
|
|||
=================== |
|||
|
|||
Odoo Apps Store: https://apps.odoo.com/apps/modules/13.0/mail_all/ |
|||
|
|||
|
|||
Tested on `Odoo 11.0 <https://github.com/odoo/odoo/commit/69c0e6be96563187c09c3748daa61347f7e29360>`_ |
@ -0,0 +1 @@ |
|||
# License MIT (https://opensource.org/licenses/MIT). |
@ -0,0 +1,30 @@ |
|||
# Copyright 2016-2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> |
|||
# Copyright 2017-2018 Artyom Losev <https://it-projects.info/team/ArtyomLosev> |
|||
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr> |
|||
# License MIT (https://opensource.org/licenses/MIT). |
|||
{ |
|||
"name": "Show all messages", |
|||
"summary": """Checkout all messages where you have access""", |
|||
"category": "Discuss", |
|||
# "live_test_url": "", |
|||
"images": ["images/1.jpg"], |
|||
"version": "13.0.1.0.1", |
|||
"application": False, |
|||
"author": "IT-Projects LLC, Pavel Romanchenko", |
|||
"support": "apps@itpp.dev", |
|||
"website": "https://it-projects.info", |
|||
"license": "Other OSI approved licence", # MIT |
|||
"price": 40.00, |
|||
"currency": "EUR", |
|||
"depends": ["mail"], |
|||
"external_dependencies": {"python": [], "bin": []}, |
|||
"data": ["views/templates.xml"], |
|||
"qweb": ["static/src/xml/menu.xml"], |
|||
"demo": [], |
|||
"post_load": None, |
|||
"pre_init_hook": None, |
|||
"post_init_hook": None, |
|||
"uninstall_hook": None, |
|||
"installable": False, |
|||
"auto_install": False, |
|||
} |
@ -0,0 +1,9 @@ |
|||
`1.0.1` |
|||
------- |
|||
|
|||
- **Fix:** Incorrect counter |
|||
|
|||
`1.0.0` |
|||
------- |
|||
|
|||
- Init version |
@ -0,0 +1,19 @@ |
|||
=================== |
|||
Show all messages |
|||
=================== |
|||
|
|||
Installation |
|||
============ |
|||
|
|||
* `Install <https://odoo-development.readthedocs.io/en/latest/odoo/usage/install-module.html>`__ this module in a usual way |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
This module does not require special configuration. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
* Open menu ``Discuss / All messages`` |
|||
* You see all messages |
@ -0,0 +1,70 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_all |
|||
# |
|||
# Translators: |
|||
# Dawid Runowski <dawrun@outlook.com>, 2019 |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-05-05 23:09+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 00:05+0000\n" |
|||
"Last-Translator: Dawid Runowski <dawrun@outlook.com>, 2019\n" |
|||
"Language-Team: German (https://www.transifex.com/it-projects-llc/teams/76080/" |
|||
"de/)\n" |
|||
"Language: de\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/mail_all.js:23 |
|||
#, fuzzy, python-format |
|||
msgid "All Messages" |
|||
msgstr "Alle Nachrichten" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/xml/menu.xml:10 |
|||
#, python-format |
|||
msgid "All messages" |
|||
msgstr "Alle Nachrichten" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:23 |
|||
#, python-format |
|||
msgid "Check that All Messages are opened" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:17 |
|||
#, python-format |
|||
msgid "Click to enter menu discuss" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:13 |
|||
#, python-format |
|||
msgid "Click to open app list" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/xml/menu.xml:20 |
|||
#, python-format |
|||
msgid "No messages" |
|||
msgstr "Keine Nachrichten" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:20 |
|||
#, fuzzy, python-format |
|||
msgid "Open All Messages" |
|||
msgstr "Alle Nachrichten" |
@ -0,0 +1,69 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_all |
|||
# |
|||
# Translators: |
|||
# Randall Castro <rcastro@treintaycinco.com>, 2018 |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-04-21 23:07+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 23:07+0000\n" |
|||
"Last-Translator: Randall Castro <rcastro@treintaycinco.com>, 2018\n" |
|||
"Language-Team: Spanish (https://www.transifex.com/it-projects-llc/" |
|||
"teams/76080/es/)\n" |
|||
"Language: es\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/mail_all.js:23 |
|||
#, fuzzy, python-format |
|||
msgid "All Messages" |
|||
msgstr "Todos los mensajes" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/xml/menu.xml:10 |
|||
#, python-format |
|||
msgid "All messages" |
|||
msgstr "Todos los mensajes" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:23 |
|||
#, python-format |
|||
msgid "Check that All Messages are opened" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:17 |
|||
#, python-format |
|||
msgid "Click to enter menu discuss" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:13 |
|||
#, python-format |
|||
msgid "Click to open app list" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/xml/menu.xml:20 |
|||
#, python-format |
|||
msgid "No messages" |
|||
msgstr "Sin mensajes" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:20 |
|||
#, fuzzy, python-format |
|||
msgid "Open All Messages" |
|||
msgstr "Todos los mensajes" |
@ -0,0 +1,64 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_all |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 12.0\n" |
|||
"Report-Msgid-Bugs-To: \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: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/mail_all.js:23 |
|||
#, python-format |
|||
msgid "All Messages" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/xml/menu.xml:10 |
|||
#, python-format |
|||
msgid "All messages" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:23 |
|||
#, python-format |
|||
msgid "Check that All Messages are opened" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:17 |
|||
#, python-format |
|||
msgid "Click to enter menu discuss" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:13 |
|||
#, python-format |
|||
msgid "Click to open app list" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/xml/menu.xml:18 |
|||
#, python-format |
|||
msgid "No messages" |
|||
msgstr "" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/test_mail_all.js:20 |
|||
#, python-format |
|||
msgid "Open All Messages" |
|||
msgstr "" |
|||
|
@ -0,0 +1,35 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_all |
|||
# |
|||
# Translators: |
|||
# Matjaz Mozetic <m.mozetic@matmoz.si>, 2019 |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-05-05 23:09+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 00:05+0000\n" |
|||
"Last-Translator: Matjaz Mozetic <m.mozetic@matmoz.si>, 2019\n" |
|||
"Language-Team: Slovenian (https://www.transifex.com/it-projects-llc/teams/76080/sl/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: sl\n" |
|||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/js/mail_all.js:49 |
|||
#: code:addons/mail_all/static/src/xml/menu.xml:7 |
|||
#, python-format |
|||
msgid "All messages" |
|||
msgstr "Vsa sporočila" |
|||
|
|||
#. module: mail_all |
|||
#. openerp-web |
|||
#: code:addons/mail_all/static/src/xml/menu.xml:15 |
|||
#, python-format |
|||
msgid "No messages" |
|||
msgstr "Ni sporočil" |
After Width: 334 | Height: 171 | Size: 11 KiB |
After Width: 300 | Height: 270 | Size: 15 KiB |
After Width: 765 | Height: 400 | Size: 22 KiB |
After Width: 100 | Height: 100 | Size: 2.1 KiB |
@ -0,0 +1,84 @@ |
|||
<section class="oe_container"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span12"> |
|||
<h2 class="oe_slogan">Show all messages</h2> |
|||
<h3 class="oe_slogan">Checkout all messages where you have access</h3> |
|||
</div> |
|||
<div class="oe_span6"> |
|||
<div class="oe_row_img oe_centered"> |
|||
<img class="oe_picture oe_screenshot" src="1.png"/> |
|||
</div> |
|||
</div> |
|||
<div class="oe_span6"> |
|||
<p class="oe_mt32"> |
|||
The module adds usual menu. |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="oe_container oe_dark"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span12"> |
|||
<p class="oe_mt32"> |
|||
This menu shows all messages. |
|||
</p> |
|||
</div> |
|||
<div class="oe_row_img oe_centered"> |
|||
<img class="oe_picture oe_screenshot" src="2.png"/> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="oe_container"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span8"> |
|||
<h2>Need our service?</h2> |
|||
<p class="oe_mt32">Contact us by <a href="mailto:apps@it-projects.info">email</a> or fill out <a href="https://www.it-projects.info/page/website.contactus " target="_blank">request form</a></p> |
|||
<ul> |
|||
<li><a href="mailto:apps@it-projects.info">apps@it-projects.info <i class="fa fa-envelope-o"></i></a></li> |
|||
<li><a href="https://www.it-projects.info/page/website.contactus " target="_blank">https://www.it-projects.info/page/website.contactus <i class="fa fa-list-alt"></i></a></li> |
|||
<li><a href="https://m.me/itprojectsllc" target="_blank">https://m.me/itprojectsllc <i class="fa fa-facebook-square"></i></a></li> |
|||
<li>skype@it-projects.info <i class="fa fa-skype"></i></li> |
|||
</ul> |
|||
</div> |
|||
<div class="oe_span4"> |
|||
<div class="stamp" style="width:200px;"> |
|||
<div style="margin-top: 15px; |
|||
position: relative; |
|||
font-family:'Vollkorn', serif; |
|||
font-size: 16px; |
|||
line-height: 25px; |
|||
text-transform: uppercase; |
|||
font-weight: bold; |
|||
color: #75526b; |
|||
border: 3px dashed #75526b; |
|||
float: left; |
|||
padding: 4px 12px; |
|||
-webkit-transform: rotate(6deg); |
|||
-o-transform: rotate(6deg); |
|||
-moz-transform: rotate(6deg); |
|||
-ms-transform: rotate(6deg);"> |
|||
Tested on Odoo<br/>12.0 community |
|||
</div> |
|||
<!--<div style="margin-top: 15px; |
|||
position: relative; |
|||
font-family:'Vollkorn', serif; |
|||
font-size: 16px; |
|||
line-height: 25px; |
|||
text-transform: uppercase; |
|||
font-weight: bold; |
|||
color: #75526b; |
|||
border: 3px dashed #75526b; |
|||
float: left; |
|||
padding: 4px 12px; |
|||
-webkit-transform: rotate(-3deg); |
|||
-o-transform: rotate(-3deg); |
|||
-moz-transform: rotate(-3deg); |
|||
-ms-transform: rotate(-3deg);"> |
|||
Tested on Odoo<br/>12.0 enterprise |
|||
</div>--> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</section> |
@ -0,0 +1,3 @@ |
|||
.o_channel_name.mail_all i { |
|||
margin-right: 4px; |
|||
} |
@ -0,0 +1,50 @@ |
|||
/* # Copyright 2016-2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev> |
|||
# Copyright 2017-2018 Artyom Losev <https://it-projects.info/team/ArtyomLosev>
|
|||
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
|
|||
# License MIT (https://opensource.org/licenses/MIT). */
|
|||
odoo.define("mail_all.all", function(require) { |
|||
"use strict"; |
|||
|
|||
var core = require("web.core"); |
|||
var Manager = require("mail.Manager"); |
|||
var Mailbox = require("mail.model.Mailbox"); |
|||
|
|||
var _t = core._t; |
|||
|
|||
Manager.include({ |
|||
_updateMailboxesFromServer: function(data) { |
|||
this._super(data); |
|||
if ( |
|||
!_.find(this.getThreads(), function(th) { |
|||
return th.getID() === "mailbox_channel_all"; |
|||
}) |
|||
) { |
|||
this._addMailbox({ |
|||
id: "channel_all", |
|||
name: _t("All Messages"), |
|||
mailboxCounter: 0, |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
_makeMessage: function(data) { |
|||
var message = this._super(data); |
|||
message._addThread("mailbox_channel_all"); |
|||
return message; |
|||
}, |
|||
}); |
|||
|
|||
Mailbox.include({ |
|||
_getThreadDomain: function() { |
|||
if (this._id === "mailbox_channel_all") { |
|||
return []; |
|||
} |
|||
return this._super(); |
|||
}, |
|||
}); |
|||
|
|||
return { |
|||
Manager: Manager, |
|||
Mailbox: Mailbox, |
|||
}; |
|||
}); |
@ -0,0 +1,33 @@ |
|||
/* # Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr> |
|||
# License MIT (https://opensource.org/licenses/MIT). */
|
|||
odoo.define("mail_all.tour", function(require) { |
|||
"use strict"; |
|||
|
|||
var tour = require("web_tour.tour"); |
|||
var core = require("web.core"); |
|||
var _t = core._t; |
|||
|
|||
var steps = [ |
|||
{ |
|||
trigger: 'a.full[href="#"]', |
|||
content: _t("Click to open app list"), |
|||
position: "bottom", |
|||
}, |
|||
{ |
|||
trigger: 'a.dropdown-item.o_app:contains("Discuss")', |
|||
content: _t("Click to enter menu discuss"), |
|||
position: "bottom", |
|||
}, |
|||
{ |
|||
content: _t("Open All Messages"), |
|||
trigger: ".o_channel_name.mail_all", |
|||
}, |
|||
{ |
|||
content: _t("Check that All Messages are opened"), |
|||
trigger: |
|||
".o_mail_discuss_title_main.o_mail_mailbox_title_all.o_mail_discuss_item.o_active", |
|||
}, |
|||
]; |
|||
|
|||
tour.register("tour_mail_all", {test: true, url: "/web"}, steps); |
|||
}); |
@ -0,0 +1,27 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!--# Copyright 2018 Artyom Losev <https://it-projects.info/team/ArtyomLosev> |
|||
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr> |
|||
# License MIT (https://opensource.org/licenses/MIT). --> |
|||
<template> |
|||
<!--Inherit Sidebar and add All messages menu item after Starred --> |
|||
<t t-extend="mail.discuss.Sidebar"> |
|||
<t t-jquery="div[data-thread-id=mailbox_starred]" t-operation="after"> |
|||
<div |
|||
t-attf-class="o_mail_discuss_title_main o_mail_mailbox_title_all o_mail_discuss_item #{(activeThreadID == 'channel_all') ? 'o_active': ''}" |
|||
data-thread-id="mailbox_channel_all" |
|||
> |
|||
<span class="o_channel_name mail_all"> <i |
|||
class="fa fa-database" |
|||
/> All messages </span> |
|||
</div> |
|||
</t> |
|||
</t> |
|||
<!--Add message about empty all messages page--> |
|||
<t t-extend="mail.widget.Thread.Empty"> |
|||
<t t-jquery="t:last" t-operation="after"> |
|||
<t t-if="thread.getID() === 'mailbox_channel_all'"> |
|||
<div class="o_thread_title">No messages</div> |
|||
</t> |
|||
</t> |
|||
</t> |
|||
</template> |
@ -0,0 +1,3 @@ |
|||
# License MIT (https://opensource.org/licenses/MIT). |
|||
|
|||
from . import test_js |
@ -0,0 +1,27 @@ |
|||
# Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev> |
|||
# Copyright 2016 manawi <https://it-projects.info/team/manawi> |
|||
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr> |
|||
# License MIT (https://opensource.org/licenses/MIT). |
|||
|
|||
import odoo.tests |
|||
|
|||
|
|||
@odoo.tests.common.at_install(True) |
|||
@odoo.tests.common.post_install(True) |
|||
class TestUi(odoo.tests.HttpCase): |
|||
def test_01_mail_all(self): |
|||
# needed because tests are run before the module is marked as |
|||
# installed. In js web will only load qweb coming from modules |
|||
# that are returned by the backend in module_boot. Without |
|||
# this you end up with js, css but no qweb. |
|||
self.env["ir.module.module"].search( |
|||
[("name", "=", "mail_all")], limit=1 |
|||
).state = "installed" |
|||
|
|||
link = "/web#action=%s" % self.ref("mail.action_discuss") |
|||
self.phantom_js( |
|||
link, |
|||
"odoo.__DEBUG__.services['web_tour.tour'].run('tour_mail_all', 1000)", |
|||
"odoo.__DEBUG__.services['web_tour.tour'].tours.tour_mail_all.ready", |
|||
login="admin", |
|||
) |
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" ?> |
|||
<!--# Copyright 2016 Ivan Yelizariev <https://it-projects.info/team/yelizariev> |
|||
# Copyright 2018 Artyom Losev <https://it-projects.info/team/ArtyomLosev> |
|||
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr> |
|||
# License MIT (https://opensource.org/licenses/MIT). --> |
|||
<odoo> |
|||
<template |
|||
id="mail_all_assets_backend" |
|||
name="mail_all_assets_backend" |
|||
inherit_id="web.assets_backend" |
|||
> |
|||
<xpath expr="." position="inside"> |
|||
<link rel="stylesheet" href="/mail_all/static/src/css/mail_all.css" /> |
|||
<script src="/mail_all/static/src/js/mail_all.js" type="text/javascript" /> |
|||
<script |
|||
src="/mail_all/static/src/js/test_mail_all.js" |
|||
type="text/javascript" |
|||
/> |
|||
</xpath> |
|||
</template> |
|||
</odoo> |
@ -0,0 +1,25 @@ |
|||
.. image:: https://itpp.dev/images/infinity-readme.png |
|||
:alt: Tested and maintained by IT Projects Labs |
|||
:target: https://itpp.dev |
|||
|
|||
=============== |
|||
Mail Archives |
|||
=============== |
|||
|
|||
Adds Archive menu, which shows sent/received messages |
|||
|
|||
Questions? |
|||
========== |
|||
|
|||
To get an assistance on this module contact us by email :arrow_right: help@itpp.dev |
|||
|
|||
Contributors |
|||
============ |
|||
* Pavel Romanchenko <apps@it-projects.info> |
|||
|
|||
=================== |
|||
|
|||
Odoo Apps Store: https://apps.odoo.com/apps/modules/13.0/mail_archives/ |
|||
|
|||
|
|||
Tested on `Odoo 12.0 <https://github.com/odoo/odoo/commit/c423e5fe047a66517a60b68874e18dc5c3697787>`_ |
@ -0,0 +1,17 @@ |
|||
{ |
|||
"name": "Mail archives", |
|||
"summary": """Adds menu to find old messages""", |
|||
"category": "Discuss", |
|||
"images": ["images/1.jpg"], |
|||
"version": "13.0.1.0.1", |
|||
"author": "IT-Projects LLC, Pavel Romanchenko", |
|||
"support": "apps@itpp.dev", |
|||
"website": "https://it-projects.info", |
|||
"license": "Other OSI approved licence", # MIT |
|||
"price": 40.00, |
|||
"currency": "EUR", |
|||
"depends": ["mail"], |
|||
"data": ["views/templates.xml"], |
|||
"qweb": ["static/src/xml/menu.xml"], |
|||
"installable": False, |
|||
} |
@ -0,0 +1,11 @@ |
|||
`1.0.1` |
|||
------- |
|||
|
|||
- **Fix:** Mails from channels were not downloaded automatically |
|||
- **Fix:** Fetching mails from another threads |
|||
- **Fix:** Incorrect counter |
|||
|
|||
`1.0.0` |
|||
------- |
|||
|
|||
- Init version |
@ -0,0 +1,20 @@ |
|||
============== |
|||
Mail Archives |
|||
============== |
|||
|
|||
Installation |
|||
============ |
|||
|
|||
* `Install <https://odoo-development.readthedocs.io/en/latest/odoo/usage/install-module.html>`__ this module in a usual way |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
This module does not require special configuration. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
* Open ``Discuss``. |
|||
* Click ``Archive``. |
|||
* Sent/received messages are displayed. |
@ -0,0 +1,36 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_archives |
|||
# |
|||
# Translators: |
|||
# Dawid Runowski <dawrun@outlook.com>, 2019 |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-05-05 23:09+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 00:05+0000\n" |
|||
"Last-Translator: Dawid Runowski <dawrun@outlook.com>, 2019\n" |
|||
"Language-Team: German (https://www.transifex.com/it-projects-llc/teams/76080/" |
|||
"de/)\n" |
|||
"Language: de\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
|
|||
#. module: mail_archives |
|||
#. openerp-web |
|||
#: code:addons/mail_archives/static/src/js/archives.js:20 |
|||
#: code:addons/mail_archives/static/src/xml/menu.xml:8 |
|||
#, python-format |
|||
msgid "Archive" |
|||
msgstr "Archiv" |
|||
|
|||
#. module: mail_archives |
|||
#. openerp-web |
|||
#: code:addons/mail_archives/static/src/xml/menu.xml:18 |
|||
#, python-format |
|||
msgid "Archive is empty" |
|||
msgstr "Archiv ist leer" |
@ -0,0 +1,35 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_archives |
|||
# |
|||
# Translators: |
|||
# Randall Castro <rcastro@treintaycinco.com>, 2018 |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-04-21 23:07+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 23:07+0000\n" |
|||
"Last-Translator: Randall Castro <rcastro@treintaycinco.com>, 2018\n" |
|||
"Language-Team: Spanish (https://www.transifex.com/it-projects-llc/" |
|||
"teams/76080/es/)\n" |
|||
"Language: es\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
|
|||
#. module: mail_archives |
|||
#. openerp-web |
|||
#: code:addons/mail_archives/static/src/js/archives.js:20 |
|||
#: code:addons/mail_archives/static/src/xml/menu.xml:8 |
|||
#, python-format |
|||
msgid "Archive" |
|||
msgstr "Archivo" |
|||
|
|||
#. module: mail_archives |
|||
#. openerp-web |
|||
#: code:addons/mail_archives/static/src/xml/menu.xml:18 |
|||
#, python-format |
|||
msgid "Archive is empty" |
|||
msgstr "Archivo está vacío" |
@ -0,0 +1,30 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_archives |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 12.0\n" |
|||
"Report-Msgid-Bugs-To: \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: mail_archives |
|||
#. openerp-web |
|||
#: code:addons/mail_archives/static/src/js/archives.js:21 |
|||
#: code:addons/mail_archives/static/src/xml/menu.xml:8 |
|||
#, python-format |
|||
msgid "Archive" |
|||
msgstr "" |
|||
|
|||
#. module: mail_archives |
|||
#. openerp-web |
|||
#: code:addons/mail_archives/static/src/xml/menu.xml:16 |
|||
#, python-format |
|||
msgid "Archive is empty" |
|||
msgstr "" |
|||
|
@ -0,0 +1,35 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_archives |
|||
# |
|||
# Translators: |
|||
# Matjaz Mozetic <m.mozetic@matmoz.si>, 2019 |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-05-05 23:09+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 00:05+0000\n" |
|||
"Last-Translator: Matjaz Mozetic <m.mozetic@matmoz.si>, 2019\n" |
|||
"Language-Team: Slovenian (https://www.transifex.com/it-projects-llc/teams/76080/sl/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: sl\n" |
|||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" |
|||
|
|||
#. module: mail_archives |
|||
#. openerp-web |
|||
#: code:addons/mail_archives/static/src/js/archives.js:68 |
|||
#: code:addons/mail_archives/static/src/xml/menu.xml:7 |
|||
#, python-format |
|||
msgid "Archive" |
|||
msgstr "Arhiv" |
|||
|
|||
#. module: mail_archives |
|||
#. openerp-web |
|||
#: code:addons/mail_archives/static/src/xml/menu.xml:15 |
|||
#, python-format |
|||
msgid "Archive is empty" |
|||
msgstr "Arhiv je prazen" |
After Width: 334 | Height: 171 | Size: 18 KiB |
After Width: 300 | Height: 270 | Size: 24 KiB |
After Width: 765 | Height: 400 | Size: 40 KiB |
After Width: 100 | Height: 100 | Size: 2.1 KiB |
@ -0,0 +1,84 @@ |
|||
<section class="oe_container"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span12"> |
|||
<h2 class="oe_slogan">Look up old mails</h2> |
|||
<h3 class="oe_slogan">Browse archived mails like in odoo 8</h3> |
|||
</div> |
|||
<div class="oe_span6"> |
|||
<div class="oe_row_img oe_centered"> |
|||
<img class="oe_picture oe_screenshot" src="1.png"/> |
|||
</div> |
|||
</div> |
|||
<div class="oe_span6"> |
|||
<p class="oe_mt32"> |
|||
The module adds usual menu. |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="oe_container oe_dark"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span12"> |
|||
<p class="oe_mt32"> |
|||
This menu shows archive messages, i.e. ones you sent or received. |
|||
</p> |
|||
</div> |
|||
<div class="oe_row_img oe_centered"> |
|||
<img class="oe_picture oe_screenshot" src="2.png"/> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="oe_container"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span8"> |
|||
<h2>Need our service?</h2> |
|||
<p class="oe_mt32">Contact us by <a href="mailto:apps@it-projects.info">email</a> or fill out <a href="https://www.it-projects.info/page/website.contactus " target="_blank">request form</a></p> |
|||
<ul> |
|||
<li><a href="mailto:apps@it-projects.info">apps@it-projects.info <i class="fa fa-envelope-o"></i></a></li> |
|||
<li><a href="https://www.it-projects.info/page/website.contactus " target="_blank">https://www.it-projects.info/page/website.contactus <i class="fa fa-list-alt"></i></a></li> |
|||
<li><a href="https://m.me/itprojectsllc" target="_blank">https://m.me/itprojectsllc <i class="fa fa-facebook-square"></i></a></li> |
|||
<li>skype@it-projects.info <i class="fa fa-skype"></i></li> |
|||
</ul> |
|||
</div> |
|||
<div class="oe_span4"> |
|||
<div class="stamp" style="width:200px;"> |
|||
<div style="margin-top: 15px; |
|||
position: relative; |
|||
font-family:'Vollkorn', serif; |
|||
font-size: 16px; |
|||
line-height: 25px; |
|||
text-transform: uppercase; |
|||
font-weight: bold; |
|||
color: #75526b; |
|||
border: 3px dashed #75526b; |
|||
float: left; |
|||
padding: 4px 12px; |
|||
-webkit-transform: rotate(6deg); |
|||
-o-transform: rotate(6deg); |
|||
-moz-transform: rotate(6deg); |
|||
-ms-transform: rotate(6deg);"> |
|||
Tested on Odoo<br/>12.0 community |
|||
</div> |
|||
<div style="margin-top: 15px; |
|||
position: relative; |
|||
font-family:'Vollkorn', serif; |
|||
font-size: 16px; |
|||
line-height: 25px; |
|||
text-transform: uppercase; |
|||
font-weight: bold; |
|||
color: #75526b; |
|||
border: 3px dashed #75526b; |
|||
float: left; |
|||
padding: 4px 12px; |
|||
-webkit-transform: rotate(-7deg); |
|||
-o-transform: rotate(-7deg); |
|||
-moz-transform: rotate(-7deg); |
|||
-ms-transform: rotate(-7deg);"> |
|||
Tested on Odoo<br/>12.0 enterprise |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</section> |
@ -0,0 +1,3 @@ |
|||
.o_channel_name.mail_archives i { |
|||
margin-right: 4px; |
|||
} |
@ -0,0 +1,91 @@ |
|||
odoo.define("mail_archives.archives", function(require) { |
|||
"use strict"; |
|||
|
|||
var core = require("web.core"); |
|||
var session = require("web.session"); |
|||
var Manager = require("mail.Manager"); |
|||
var Mailbox = require("mail.model.Mailbox"); |
|||
var SearchableThread = require("mail.model.SearchableThread"); |
|||
|
|||
var _t = core._t; |
|||
|
|||
Manager.include({ |
|||
_updateMailboxesFromServer: function(data) { |
|||
this._super(data); |
|||
if ( |
|||
!_.find(this.getThreads(), function(th) { |
|||
return th.getID() === "mailbox_channel_archive"; |
|||
}) |
|||
) { |
|||
this._addMailbox({ |
|||
id: "channel_archive", |
|||
name: _t("Archive"), |
|||
mailboxCounter: 0, |
|||
}); |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
SearchableThread.include({ |
|||
_fetchMessages: function(pDomain, loadMore) { |
|||
var self = this; |
|||
if (this._id !== "mailbox_channel_archive") { |
|||
return this._super(pDomain, loadMore); |
|||
} |
|||
|
|||
// This is a copy-paste from super method
|
|||
var domain = this._getThreadDomain(); |
|||
var cache = this._getCache(pDomain); |
|||
if (pDomain) { |
|||
domain = domain.concat(pDomain || []); |
|||
} |
|||
if (loadMore) { |
|||
var minMessageID = cache.messages[0].getID(); |
|||
domain = [["id", "<", minMessageID]].concat(domain); |
|||
} |
|||
return this._rpc({ |
|||
model: "mail.message", |
|||
method: "message_fetch", |
|||
args: [domain], |
|||
kwargs: this._getFetchMessagesKwargs(), |
|||
}).then(function(messages) { |
|||
// Except this function. It adds the required thread to downloaded messages
|
|||
_.each(messages, function(m) { |
|||
m.channel_ids.push("mailbox_channel_archive"); |
|||
}); |
|||
if (!cache.allHistoryLoaded) { |
|||
cache.allHistoryLoaded = messages.length < self._FETCH_LIMIT; |
|||
} |
|||
cache.loaded = true; |
|||
_.each(messages, function(message) { |
|||
self.call("mail_service", "addMessage", message, { |
|||
silent: true, |
|||
domain: pDomain, |
|||
}); |
|||
}); |
|||
cache = self._getCache(pDomain || []); |
|||
return cache.messages; |
|||
}); |
|||
}, |
|||
}); |
|||
|
|||
Mailbox.include({ |
|||
_getThreadDomain: function() { |
|||
if (this._id === "mailbox_channel_archive") { |
|||
return [ |
|||
"|", |
|||
"|", |
|||
["partner_ids", "in", [session.partner_id]], |
|||
["author_id", "in", [session.partner_id]], |
|||
["channel_ids.channel_partner_ids", "in", [session.partner_id]], |
|||
]; |
|||
} |
|||
return this._super(); |
|||
}, |
|||
}); |
|||
|
|||
return { |
|||
Manager: Manager, |
|||
Mailbox: Mailbox, |
|||
}; |
|||
}); |
@ -0,0 +1,24 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<template> |
|||
<!--Inherit Sidebar and add Archive menu item after Starred --> |
|||
<t t-extend="mail.discuss.Sidebar"> |
|||
<t t-jquery="div[data-thread-id=mailbox_starred]" t-operation="after"> |
|||
<div |
|||
t-attf-class="o_mail_discuss_title_main o_mail_discuss_item #{(activeThreadID == 'channel_archive') ? 'o_active': ''}" |
|||
data-thread-id="mailbox_channel_archive" |
|||
> |
|||
<span class="o_channel_name mail_archives"> <i |
|||
class="fa fa-archive" |
|||
/> Archive </span> |
|||
</div> |
|||
</t> |
|||
</t> |
|||
<!--Add message about empty archive page--> |
|||
<t t-extend="mail.widget.Thread.Empty"> |
|||
<t t-jquery="t:last" t-operation="after"> |
|||
<t t-if="thread.getID() === 'mailbox_channel_archive'"> |
|||
<div class="o_thread_title">Archive is empty</div> |
|||
</t> |
|||
</t> |
|||
</t> |
|||
</template> |
@ -0,0 +1 @@ |
|||
from . import test_js |
@ -0,0 +1,31 @@ |
|||
from werkzeug import url_encode |
|||
|
|||
import odoo.tests |
|||
|
|||
|
|||
@odoo.tests.common.at_install(True) |
|||
@odoo.tests.common.post_install(True) |
|||
class TestUi(odoo.tests.HttpCase): |
|||
def test_01_mail_archives(self): |
|||
|
|||
# needed because tests are run before the module is marked as |
|||
# installed. In js web will only load qweb coming from modules |
|||
# that are returned by the backend in module_boot. Without |
|||
# this you end up with js, css but no qweb. |
|||
self.env["ir.module.module"].search( |
|||
[("name", "=", "mail_archives")], limit=1 |
|||
).state = "installed" |
|||
|
|||
# wait till page loaded and then click and wait again |
|||
code = """ |
|||
setTimeout(function () { |
|||
console.log($(".mail_archives").length && 'ok' || 'error'); |
|||
}, 3000); |
|||
""" |
|||
link = "/web#%s" % url_encode({"action": "mail.action_discuss"}) |
|||
self.phantom_js( |
|||
link, |
|||
code, |
|||
"odoo.__DEBUG__.services['web_tour.tour'].tours.mail_tour.ready", |
|||
login="admin", |
|||
) |
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" ?> |
|||
<openerp> |
|||
<data> |
|||
<template |
|||
id="res_partner_mails_count_assets_backend" |
|||
name="res_partner_mails_count_assets_backend" |
|||
inherit_id="web.assets_backend" |
|||
> |
|||
<xpath expr="." position="inside"> |
|||
<link |
|||
rel="stylesheet" |
|||
href="/mail_archives/static/src/css/archives.css" |
|||
/> |
|||
<script |
|||
src="/mail_archives/static/src/js/archives.js" |
|||
type="text/javascript" |
|||
/> |
|||
</xpath> |
|||
</template> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,34 @@ |
|||
.. image:: https://itpp.dev/images/infinity-readme.png |
|||
:alt: Tested and maintained by IT Projects Labs |
|||
:target: https://itpp.dev |
|||
|
|||
=========== |
|||
Mail Base |
|||
=========== |
|||
|
|||
* makes built-in mail js features extendable. |
|||
* handles ``search_default_*`` parameters in context. |
|||
* fixes toggling left bar |
|||
* fixes Recipients field. Out-of-box this field could be empty. |
|||
|
|||
One can say, that the module do this todo from `addons/mail/static/src/js/chat_manager.js <https://github.com/odoo/odoo/blob/9.0/addons/mail/static/src/js/chat_manager.js#L57>`__ |
|||
|
|||
// to do: move this to mail.utils |
|||
|
|||
Note. Due to odoo restrictions, module makes mail initialization again. That is browser loads emoji and other chat data twice. This is the only way to make Mail feature extendable. |
|||
|
|||
Questions? |
|||
========== |
|||
|
|||
To get an assistance on this module contact us by email :arrow_right: help@itpp.dev |
|||
|
|||
Contributors |
|||
============ |
|||
* Pavel Romanchenko <apps@it-projects.info> |
|||
|
|||
=================== |
|||
|
|||
Odoo Apps Store: https://apps.odoo.com/apps/modules/11.0/mail_base/ |
|||
|
|||
|
|||
Tested on `Odoo 11.0 <https://github.com/odoo/odoo/commit/ecbf7aa4714479229658d14cce28fa00376ed390>`_ |
@ -0,0 +1,2 @@ |
|||
from . import models |
|||
from . import controllers |
@ -0,0 +1,16 @@ |
|||
{ |
|||
"name": "Mail Base", |
|||
"summary": """Makes Mail extendable""", |
|||
"category": "Discuss", |
|||
"images": [], |
|||
"version": "13.0.1.0.2", |
|||
"author": "IT-Projects LLC, Pavel Romanchenko", |
|||
"support": "apps@itpp.dev", |
|||
"website": "https://it-projects.info", |
|||
"license": "Other OSI approved licence", # MIT |
|||
"price": 9.00, |
|||
"currency": "EUR", |
|||
"depends": ["base", "mail"], |
|||
"data": ["views/templates.xml"], |
|||
"installable": False, |
|||
} |
@ -0,0 +1 @@ |
|||
from . import main |
@ -0,0 +1,15 @@ |
|||
from odoo.http import request |
|||
|
|||
from odoo.addons.bus.controllers.main import BusController |
|||
|
|||
|
|||
class MailChatController(BusController): |
|||
# ----------------------------- |
|||
# Extends BUS Controller Poll |
|||
# ----------------------------- |
|||
|
|||
def _poll(self, dbname, channels, last, options): |
|||
if request.session.uid: |
|||
channels.append((request.db, "mail_base.mail_sent")) |
|||
|
|||
return super(MailChatController, self)._poll(dbname, channels, last, options) |
@ -0,0 +1,22 @@ |
|||
`1.0.4` |
|||
------- |
|||
**FIX**: issue related to clear cache |
|||
|
|||
`1.0.3` |
|||
------- |
|||
**FIX**: error on clicking messages preview after sending new message |
|||
|
|||
`1.0.2` |
|||
------- |
|||
|
|||
- **FIX**: fixed an error with unsubscribing from channel or closing dialog window |
|||
|
|||
`1.0.1` |
|||
------- |
|||
|
|||
- **FIX**: clear messages cache on sending message via Mail Composer. Otherwise Sent, Arhives menus will have new message until user refresh whole web page |
|||
|
|||
`1.0.0` |
|||
------- |
|||
|
|||
- Init version |
@ -0,0 +1,18 @@ |
|||
========================= |
|||
Mail Base |
|||
========================= |
|||
|
|||
Installation |
|||
============ |
|||
|
|||
* `Install <https://odoo-development.readthedocs.io/en/latest/odoo/usage/install-module.html>`__ this module in a usual way |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
This module does not require special configuration. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
* To use this module you need either install module that depends on it or create new module. |
@ -0,0 +1,31 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_base |
|||
# |
|||
# Translators: |
|||
# Dawid Runowski <dawrun@outlook.com>, 2019 |
|||
# Ermin Trevisan <trevi@twanda.com>, 2019 |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-05-05 23:09+0000\n" |
|||
"PO-Revision-Date: 2017-11-28 13:59+0000\n" |
|||
"Last-Translator: Ermin Trevisan <trevi@twanda.com>, 2019\n" |
|||
"Language-Team: German (https://www.transifex.com/it-projects-llc/teams/76080/de/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: de\n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_compose_message |
|||
msgid "Email composition wizard" |
|||
msgstr "Assistent für die Email-Gestaltung" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_message |
|||
msgid "Message" |
|||
msgstr "Nachricht" |
@ -0,0 +1,29 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_base |
|||
# |
|||
# Translators: |
|||
# Randall Castro <rcastro@treintaycinco.com>, 2018 |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-04-21 23:07+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 23:07+0000\n" |
|||
"Last-Translator: Randall Castro <rcastro@treintaycinco.com>, 2018\n" |
|||
"Language-Team: Spanish (https://www.transifex.com/it-projects-llc/teams/76080/es/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: es\n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_compose_message |
|||
msgid "Email composition wizard" |
|||
msgstr "Asistente para composición de correo" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_message |
|||
msgid "Message" |
|||
msgstr "Mensaje" |
@ -0,0 +1,29 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_base |
|||
# |
|||
# Translators: |
|||
# Translation Bot <i18n-bot@it-projects.info>, 2017 |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 10.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2017-07-17 20:20+0000\n" |
|||
"PO-Revision-Date: 2017-07-17 20:20+0000\n" |
|||
"Last-Translator: Translation Bot <i18n-bot@it-projects.info>, 2017\n" |
|||
"Language-Team: Italian (https://www.transifex.com/it-projects-llc/teams/76080/it/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: it\n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_compose_message |
|||
msgid "Email composition wizard" |
|||
msgstr "Componi Email" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_message |
|||
msgid "Message" |
|||
msgstr "Messaggio" |
@ -0,0 +1,29 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_base |
|||
# |
|||
# Translators: |
|||
# Translation Bot <i18n-bot@it-projects.info>, 2018 |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-04-21 00:05+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 00:05+0000\n" |
|||
"Last-Translator: Translation Bot <i18n-bot@it-projects.info>, 2018\n" |
|||
"Language-Team: Portuguese (https://www.transifex.com/it-projects-llc/teams/76080/pt/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: pt\n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_compose_message |
|||
msgid "Email composition wizard" |
|||
msgstr "Assistente de composição de Email" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_message |
|||
msgid "Message" |
|||
msgstr "Mensagem" |
@ -0,0 +1,29 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_base |
|||
# |
|||
# Translators: |
|||
# Translation Bot <i18n-bot@it-projects.info>, 2018 |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-04-21 00:05+0000\n" |
|||
"PO-Revision-Date: 2018-04-21 00:05+0000\n" |
|||
"Last-Translator: Translation Bot <i18n-bot@it-projects.info>, 2018\n" |
|||
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/it-projects-llc/teams/76080/pt_BR/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: pt_BR\n" |
|||
"Plural-Forms: nplurals=2; plural=(n > 1);\n" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_compose_message |
|||
msgid "Email composition wizard" |
|||
msgstr "Assistente de Composição de Email" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_message |
|||
msgid "Message" |
|||
msgstr "Mensagem" |
@ -0,0 +1,29 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_base |
|||
# |
|||
# Translators: |
|||
# Ivan Yelizariev <yelizariev@it-projects.info>, 2017 |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 10.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2017-07-28 14:34+0000\n" |
|||
"PO-Revision-Date: 2017-07-28 14:34+0000\n" |
|||
"Last-Translator: Ivan Yelizariev <yelizariev@it-projects.info>, 2017\n" |
|||
"Language-Team: Russian (https://www.transifex.com/it-projects-llc/teams/76080/ru/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: ru\n" |
|||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_compose_message |
|||
msgid "Email composition wizard" |
|||
msgstr "" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_message |
|||
msgid "Message" |
|||
msgstr "Сообщение" |
@ -0,0 +1,31 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * mail_base |
|||
# |
|||
# Translators: |
|||
# Translation Bot <i18n-bot@it-projects.info>, 2017 |
|||
# Matjaz Mozetic <m.mozetic@matmoz.si>, 2019 |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 11.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2018-05-05 23:09+0000\n" |
|||
"PO-Revision-Date: 2017-11-28 13:59+0000\n" |
|||
"Last-Translator: Matjaz Mozetic <m.mozetic@matmoz.si>, 2019\n" |
|||
"Language-Team: Slovenian (https://www.transifex.com/it-projects-llc/teams/76080/sl/)\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Language: sl\n" |
|||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_compose_message |
|||
msgid "Email composition wizard" |
|||
msgstr "Čarovnik za sestavljanje sporočil" |
|||
|
|||
#. module: mail_base |
|||
#: model:ir.model,name:mail_base.model_mail_message |
|||
msgid "Message" |
|||
msgstr "Sporočilo" |
@ -0,0 +1,29 @@ |
|||
from odoo import models |
|||
|
|||
|
|||
class MailMessage(models.Model): |
|||
_inherit = "mail.message" |
|||
|
|||
def write(self, values): |
|||
if values.get("needaction_partner_ids"): |
|||
if not values.get("partner_ids"): |
|||
values["partner_ids"] = [] |
|||
for triplet in values.get("needaction_partner_ids"): |
|||
if triplet[0] == 6: |
|||
for i in triplet[2]: |
|||
values["partner_ids"].append((4, i, False)) |
|||
return super(MailMessage, self).write(values) |
|||
|
|||
|
|||
class MailComposer(models.TransientModel): |
|||
|
|||
_inherit = "mail.compose.message" |
|||
|
|||
def send_mail(self, auto_commit=False): |
|||
res = super(MailComposer, self).send_mail(auto_commit=auto_commit) |
|||
notification = {} |
|||
self.env["bus.bus"].sendone( |
|||
(self._cr.dbname, "mail_base.mail_sent"), notification |
|||
) |
|||
|
|||
return res |
After Width: 100 | Height: 100 | Size: 2.1 KiB |
1418
mail_base/static/lib/base.js
File diff suppressed because it is too large
View File
@ -0,0 +1 @@ |
|||
from . import test_default |
@ -0,0 +1,17 @@ |
|||
import odoo.tests |
|||
|
|||
|
|||
@odoo.tests.common.at_install(False) |
|||
@odoo.tests.common.post_install(True) |
|||
class TestUi(odoo.tests.HttpCase): |
|||
def test_01_mail_base(self): |
|||
# wait till page loaded |
|||
code = """ |
|||
setTimeout(function () { |
|||
console.log('ok'); |
|||
}, 1000); |
|||
""" |
|||
link = "/web#action=%s" % self.ref("mail.mail_channel_action_client_chat") |
|||
self.phantom_js( |
|||
link, code, "odoo.__DEBUG__.services['mail_base.base']", login="admin" |
|||
) |
@ -0,0 +1,14 @@ |
|||
<?xml version="1.0" ?> |
|||
<openerp> |
|||
<data> |
|||
<template |
|||
id="mail_base_assets_backend" |
|||
name="mail_base_assets_backend" |
|||
inherit_id="web.assets_backend" |
|||
> |
|||
<xpath expr="." position="inside"> |
|||
<script src="/mail_base/static/lib/base.js" type="text/javascript" /> |
|||
</xpath> |
|||
</template> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,8 @@ |
|||
.. image:: https://itpp.dev/images/infinity-readme.png |
|||
:alt: Tested and maintained by IT Projects Labs |
|||
:target: https://itpp.dev |
|||
|
|||
Check mail immediately |
|||
====================== |
|||
|
|||
Description: https://apps.odoo.com/apps/modules/8.0/mail_check_immediately/ |
@ -0,0 +1 @@ |
|||
from . import models |
@ -0,0 +1,15 @@ |
|||
{ |
|||
"name": "Check mail immediately", |
|||
"vesion": "13.0.1.0.1", |
|||
"author": "IT-Projects LLC, Ivan Yelizariev", |
|||
"license": "Other OSI approved licence", # MIT |
|||
"category": "Discuss", |
|||
"support": "apps@itpp.dev", |
|||
"website": "https://twitter.com/yelizariev", |
|||
"price": 9.00, |
|||
"currency": "EUR", |
|||
"depends": ["base", "web", "fetchmail", "mail"], |
|||
"data": ["views.xml"], |
|||
"qweb": ["static/src/xml/main.xml"], |
|||
"installable": False, |
|||
} |
@ -0,0 +1,4 @@ |
|||
`1.0.1` |
|||
------- |
|||
|
|||
- FIX: incorrectly displayed last updated time when multiple threads (--workers) |
@ -0,0 +1,80 @@ |
|||
import datetime |
|||
|
|||
from odoo import api, exceptions, fields, models, tools |
|||
from odoo.tools.translate import _ |
|||
|
|||
|
|||
class FetchMailServer(models.Model): |
|||
_inherit = "fetchmail.server" |
|||
_name = "fetchmail.server" |
|||
|
|||
_last_updated = None |
|||
|
|||
run_time = fields.Datetime(string="Launch time") |
|||
|
|||
def _run_time(self): |
|||
if not self._last_updated: |
|||
self._last_updated = tools.datetime.now() |
|||
|
|||
src_tstamp_str = self._last_updated.strftime( |
|||
tools.misc.DEFAULT_SERVER_DATETIME_FORMAT |
|||
) |
|||
src_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT |
|||
dst_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT |
|||
dst_tz_name = self._context.get("tz") or self.env.user.tz |
|||
_now = tools.misc.server_to_local_timestamp( |
|||
src_tstamp_str, src_format, dst_format, dst_tz_name |
|||
) |
|||
|
|||
return _now |
|||
|
|||
@api.model |
|||
def _fetch_mails(self): |
|||
|
|||
if self._context.get("run_fetchmail_manually"): |
|||
# if interval less than 5 seconds |
|||
if self._last_updated and ( |
|||
datetime.datetime.now() - self._last_updated |
|||
) < datetime.timedelta(0, 5): |
|||
raise exceptions.Warning( |
|||
_("Error"), _("Task can be started no earlier than 5 seconds.") |
|||
) |
|||
|
|||
super(FetchMailServer, self)._fetch_mails() |
|||
|
|||
res = ( |
|||
self.env["fetchmail.server"] |
|||
.sudo() |
|||
.with_context(tz=self.env.user.tz) |
|||
.search([("state", "=", "done")]) |
|||
) |
|||
if res: |
|||
res[0].run_time = self._run_time() |
|||
|
|||
|
|||
class FetchMailImmediately(models.AbstractModel): |
|||
|
|||
_name = "fetch_mail.imm" |
|||
|
|||
@api.model |
|||
def get_last_update_time(self): |
|||
res = ( |
|||
self.env["fetchmail.server"] |
|||
.sudo() |
|||
.with_context(tz=self.env.user.tz) |
|||
.search([("state", "=", "done")]) |
|||
) |
|||
array = [r.run_time for r in res] |
|||
if array: |
|||
return array[0] |
|||
else: |
|||
return None |
|||
|
|||
@api.model |
|||
def run_fetchmail_manually(self): |
|||
|
|||
fetchmail_task = self.env.ref("fetchmail.ir_cron_mail_gateway_action") |
|||
fetchmail_model = self.env["fetchmail.server"].sudo() |
|||
|
|||
fetchmail_task._try_lock() |
|||
fetchmail_model.with_context(run_fetchmail_manually=True)._fetch_mails() |
After Width: 100 | Height: 100 | Size: 2.1 KiB |
@ -0,0 +1,49 @@ |
|||
<section class="oe_container"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span12"> |
|||
<h2 class="oe_slogan">Check mail immediately</h2> |
|||
<h3 class="oe_slogan">Keep your inbox up to date</h3> |
|||
</div> |
|||
|
|||
<div class="oe_span12"> |
|||
<div class="oe_demo oe_picture oe_screenshot"> |
|||
<img src="screenshot.png?"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="oe_container oe_dark"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span12"> |
|||
<h2>Protect your business</h2> |
|||
</div> |
|||
<div class="oe_span6"> |
|||
<p class="oe_mt32"> |
|||
<a href="https://github.com/odoo/odoo/issues/7464">Sometimes</a> odoo mail fetching system doesn't work for really long time. It could be a real problem, if you will not notice it on time. Until this issue is fixed, you can restart odoo every time when you see that last fetching time is more than 5 minutes. |
|||
</p> |
|||
</div> |
|||
|
|||
<div class="oe_span6"> |
|||
<div class="oe_picture"> |
|||
<img src="issue.png?3"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="oe_container"> |
|||
<div class="oe_row oe_spaced"> |
|||
<div class="oe_span12"> |
|||
<h2>Need our service?</h2> |
|||
<p class="oe_mt32">Contact us by <a href="mailto:apps@it-projects.info">email</a> or fill out <a href="https://www.it-projects.info/page/website.contactus " target="_blank">request form</a></p> |
|||
<ul> |
|||
<li><a href="mailto:apps@it-projects.info">apps@it-projects.info <i class="fa fa-envelope-o"></i></a></li> |
|||
<li><a href="https://www.it-projects.info/page/website.contactus " target="_blank"> |
|||
https://www.it-projects.info/page/website.contactus <i class="fa fa-list-alt"></i></a></li> |
|||
<li><a href="https://m.me/itprojectsllc" target="_blank">https://m.me/itprojectsllc <i class="fa fa-facebook-square"></i></a></li> |
|||
<li>skype@it-projects.info <i class="fa fa-skype"></i></li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</section> |
After Width: 217 | Height: 140 | Size: 8.5 KiB |
After Width: 840 | Height: 447 | Size: 88 KiB |
@ -0,0 +1,60 @@ |
|||
openerp.mail_check_immediately = function(instance, local) { |
|||
"use strict"; |
|||
instance.mail.Wall.include({ |
|||
init: function() { |
|||
this._super.apply(this, arguments); |
|||
|
|||
var _this = this; |
|||
|
|||
this.imm_model = new instance.web.Model("fetch_mail.imm"); |
|||
this.events["click a.oe_fetch_new_mails"] = function() { |
|||
_this.run_fetchmail_manually(); |
|||
}; |
|||
}, |
|||
|
|||
start: function() { |
|||
var _this = this; |
|||
|
|||
this._super(); |
|||
|
|||
this.get_last_fetched_time(); |
|||
|
|||
this.get_time_loop = setInterval(function() { |
|||
_this.get_last_fetched_time(); |
|||
}, 30000); |
|||
}, |
|||
|
|||
run_fetchmail_manually: function() { |
|||
var _this = this; |
|||
|
|||
this.imm_model |
|||
.call("run_fetchmail_manually", { |
|||
context: new instance.web.CompoundContext(), |
|||
}) |
|||
.then(function() { |
|||
_this.get_last_fetched_time(); |
|||
}); |
|||
}, |
|||
|
|||
get_last_fetched_time: function() { |
|||
var _this = this; |
|||
this.imm_model |
|||
.call("get_last_update_time", { |
|||
context: new instance.web.CompoundContext(), |
|||
}) |
|||
.then(function(res) { |
|||
var value = null; |
|||
if (res) value = $.timeago(res); |
|||
value = value || "undefined"; |
|||
_this.$el |
|||
.find("span.oe_view_manager_fetch_mail_imm_field") |
|||
.html(value); |
|||
}); |
|||
}, |
|||
|
|||
destroy: function() { |
|||
clearInterval(this.get_time_loop); |
|||
this._super.apply(this, arguments); |
|||
}, |
|||
}); |
|||
}; |
@ -0,0 +1,29 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<templates> |
|||
<t t-name="fetch_mail_immediately.header"> |
|||
<tr class="oe_header_row"> |
|||
<td t-att-colspan="colspan or '3'"> |
|||
<div class="oe_view_manager_fetch_mail_imm"> |
|||
<em> |
|||
<span>Mails fetched:</span> |
|||
<a |
|||
href="#" |
|||
class="oe_fetch_new_mails" |
|||
title="Click to fetch mails now" |
|||
> |
|||
<span class="oe_view_manager_fetch_mail_imm_field" /> |
|||
</a> |
|||
</em> |
|||
</div> |
|||
</td> |
|||
<td /> |
|||
</tr> |
|||
</t> |
|||
<t t-extend="mail.wall"> |
|||
<t t-jquery="tr.oe_header_row_top" t-operation="after"> |
|||
<t t-call="fetch_mail_immediately.header"> |
|||
<t t-set="colspan" t-value="2" /> |
|||
</t> |
|||
</t> |
|||
</t> |
|||
</templates> |
@ -0,0 +1,16 @@ |
|||
<openerp> |
|||
<data> |
|||
<template |
|||
id="assets_backend_inherited_check_mail" |
|||
name="Check mail immediately bar" |
|||
inherit_id="web.assets_backend" |
|||
> |
|||
<xpath expr="." position="inside"> |
|||
<script |
|||
type="text/javascript" |
|||
src="/mail_check_immediately/static/src/js/main.js" |
|||
/> |
|||
</xpath> |
|||
</template> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,48 @@ |
|||
.. image:: https://itpp.dev/images/infinity-readme.png |
|||
:alt: Tested and maintained by IT Projects Labs |
|||
:target: https://itpp.dev |
|||
|
|||
Fix mail error 553 |
|||
================== |
|||
|
|||
Module updates 'FROM' field to portal@MYDOMAIN.COM value in order to fix 553 error on a mail service that checks FROM field. |
|||
|
|||
E.g: |
|||
|
|||
* Customer send email from USER@CUSTOMER.com to info@MYDOMAIN.COM |
|||
* odoo accept email and try to send notifcation to related odoo users. E.g to admin@gmail.com. |
|||
* By default odoo prepare notification email with parameters as follows: |
|||
|
|||
* FROM: user@CUSTOMER.com |
|||
* TO: admin@gmail.com |
|||
|
|||
if you mail service provider, e.g. pdd.yandex.ru, doesn't allow emails with a FROM value differ from ...@MYDOMAIN.COM, then you get 553. This is why you need to update FROM value to portal@MYDOMAIN.COM |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
You can configure default alias at Settings -> System Parameters -> mail.catchall.alias_from |
|||
You can configure name for default alias at Settings -> System Parameters -> mail.catchall.name_alias_from |
|||
|
|||
You can configure name for default alias at Settings -> System Parameters -> mail.catchall.name_alias_from |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
The module is consist of redefined send function from mail.mail |
|||
model. So it is just copy pasted source code with some |
|||
modification. This function is changed very rarely, but sometime it |
|||
can happens and the module should be updated. You can check commits |
|||
for mail_mail.py here: |
|||
https://github.com/odoo/odoo/commits/8.0/addons/mail/mail_mail.py |
|||
|
|||
Tested on `Odoo 8.0 <https://github.com/odoo/odoo/commit/d023c079ed86468436f25da613bf486a4a17d625>`_ |
|||
|
|||
Status |
|||
====== |
|||
|
|||
Related issues at odoo's tracker: |
|||
* https://github.com/odoo/odoo/issues/5864 |
|||
* https://github.com/odoo/odoo/issues/3347 |
|||
|
|||
Fix: https://github.com/odoo-dev/odoo/commit/a4597fe34fcfa8dae28b156410080346bb33af33 |
@ -0,0 +1 @@ |
|||
from . import mail_fix_553 |