Merge PR #1213 into 16.0

Signed-off-by yajo
pull/1214/head
OCA-git-bot 2023-09-04 08:14:16 +00:00
commit 77e628f528
21 changed files with 1082 additions and 0 deletions

View File

@ -0,0 +1,139 @@
========================
Deferred Message Posting
========================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
:target: https://github.com/OCA/social/tree/15.0/mail_post_defer
:alt: OCA/social
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/social-15-0/social-15-0-mail_post_defer
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/205/15.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module enhances mail threads by using the mail queue by default.
Without this module, Odoo attempts to notify recipients of your message immediately.
If your mail server is slow or you have many followers, this can mean a lot of time.
Install this module and make Odoo more snappy!
All emails will be kept in the outgoing queue by at least 30 seconds,
giving you some time to re-think what you wrote. During that time,
you can still delete the message and start again.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Configuration
=============
You need to do nothing. The module is configured appropriately out of the box.
The mail queue processing is made by a cron job. This is normal Odoo behavior,
not specific to this module. However, since you will start using that queue for
every message posted by any user in any thread, this module configures that job
to execute every minute by default.
You can still change that cadence after installing the module (although it is
not recommended). To do so:
#. Log in with an administrator user.
#. Activate developer mode.
#. Go to *Settings > Technical > Automation > Scheduled Actions*.
#. Edit the action named "Mail: Email Queue Manager".
#. Lower down the frequency in the field *Execute Every*. Recommended: 1 minute.
Usage
=====
To use this module, you need to:
#. Go to the form view of any record that has a mail thread. It can be a partner, for example.
#. Post a message.
The mail is now in the outgoing mail queue. It will be there for at least 30
seconds. It will be really sent the next time the "Mail: Email Queue Manager"
cron job is executed.
While the message has not been yet sent:
#. Hover over the little envelope. You will see a paper airplane icon,
indicating it is still outgoing.
#. Hover over the message and click on the little trash icon to delete it.
Mails will not be sent.
Known issues / Roadmap
======================
* Add minimal deferring time configuration if it ever becomes necessary. See
https://github.com/OCA/social/pull/1001#issuecomment-1461581573 for the
rationale behind current hardcoded value of 30 seconds.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/social/issues/new?body=module:%20mail_post_defer%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Moduon
Contributors
~~~~~~~~~~~~
* Jairo Llopis (https://www.moduon.team/)
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-Yajo| image:: https://github.com/Yajo.png?size=40px
:target: https://github.com/Yajo
:alt: Yajo
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-Yajo|
This module is part of the `OCA/social <https://github.com/OCA/social/tree/15.0/mail_post_defer>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1,2 @@
from . import models
from .hooks import post_init_hook

View File

@ -0,0 +1,25 @@
# Copyright 2022-2023 Moduon Team S.L. <info@moduon.team>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
{
"name": "Deferred Message Posting",
"summary": "Faster and cancellable outgoing messages",
"version": "16.0.1.0.0",
"development_status": "Alpha",
"category": "Productivity/Discuss",
"website": "https://github.com/OCA/social",
"author": "Moduon, Odoo Community Association (OCA)",
"maintainers": ["Yajo"],
"license": "LGPL-3",
"depends": [
"mail",
],
"post_init_hook": "post_init_hook",
"assets": {
# This could go in mail.assets_messaging, but that's included in
# mail.assets_discuss_public and we don't want the public to be able to
# edit their messages; only the backend.
"web.assets_backend": [
"mail_post_defer/static/src/**/*.js",
],
},
}

View File

@ -0,0 +1,23 @@
# Copyright 2022-2023 Moduon Team S.L. <info@moduon.team>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
import logging
from odoo import SUPERUSER_ID, api
_logger = logging.getLogger(__name__)
def post_init_hook(cr, registry):
"""Increase cadence of mail queue cron."""
env = api.Environment(cr, SUPERUSER_ID, {})
try:
cron = env.ref("mail.ir_cron_mail_scheduler_action")
except ValueError:
_logger.warning(
"Couldn't find the standard mail scheduler cron. "
"Maybe no mails will be ever sent!"
)
else:
_logger.info("Setting mail queue cron cadence to 1 minute")
cron.interval_number = 1
cron.interval_type = "minutes"

View File

@ -0,0 +1,31 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mail_post_defer
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.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_post_defer
#: model:ir.model,name:mail_post_defer.model_mail_thread
msgid "Email Thread"
msgstr ""
#. module: mail_post_defer
#: model:ir.model,name:mail_post_defer.model_mail_message
msgid "Message"
msgstr ""
#. module: mail_post_defer
#. openerp-web
#: code:addons/mail_post_defer/static/src/xml/message.xml:0
#, python-format
msgid "messageActionList.message.canBeEdited"
msgstr ""

View File

@ -0,0 +1,2 @@
from . import mail_message
from . import mail_thread

View File

@ -0,0 +1,16 @@
# Copyright 2022-2023 Moduon Team S.L. <info@moduon.team>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
from odoo import models
class MailMessage(models.Model):
_inherit = "mail.message"
def _cleanup_side_records(self):
"""Delete pending outgoing mails."""
self.mail_ids.filtered(lambda mail: mail.state == "outgoing").unlink()
self.env["mail.message.schedule"].search(
[("mail_message_id", "in", self.ids)]
).unlink()
return super()._cleanup_side_records()

View File

@ -0,0 +1,70 @@
# Copyright 2022-2023 Moduon Team S.L. <info@moduon.team>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
from datetime import timedelta
from odoo import _, fields, models
from odoo.exceptions import UserError
class MailThread(models.AbstractModel):
_inherit = "mail.thread"
def message_post(self, **kwargs):
"""Post messages using queue by default."""
_self = self
force_send = self.env.context.get("mail_notify_force_send") or kwargs.get(
"force_send", False
)
kwargs.setdefault("force_send", force_send)
if not force_send:
# If deferring message, give the user some minimal time to revert it
_self = self.with_context(mail_defer_seconds=30)
return super(MailThread, _self).message_post(**kwargs)
def _notify_thread(self, message, msg_vals=False, **kwargs):
"""Defer emails by default."""
defer_seconds = self.env.context.get("mail_defer_seconds")
if defer_seconds:
kwargs.setdefault(
"scheduled_date",
fields.Datetime.now() + timedelta(seconds=defer_seconds),
)
return super()._notify_thread(message, msg_vals=msg_vals, **kwargs)
def _check_can_update_message_content(self, messages):
"""Allow updating unsent messages.
Upstream Odoo only allows updating notes. We want to be able to update
any message that is not sent yet. When a message is scheduled,
notifications and mails will still not exist. Another possibility is
that they exist but are not sent yet. In those cases, we are still on
time to update it.
"""
try:
# If upstream allows editing, we are done
return super()._check_can_update_message_content(messages)
except UserError:
# Repeat upstream checks that are still valid for us
if messages.tracking_value_ids:
raise
if any(message.message_type != "comment" for message in messages):
raise
# Check that no notification or mail has been sent yet
if any(
ntf.notification_status == "sent" for ntf in messages.notification_ids
):
raise UserError(
_("Cannot modify message; notifications were already sent.")
) from None
if any(mail.state in {"sent", "received"} for mail in messages.mail_ids):
raise UserError(
_("Cannot modify message; notifications were already sent.")
) from None
def _message_update_content(self, *args, **kwargs):
"""Defer messages by extra 30 seconds after updates."""
kwargs.setdefault(
"scheduled_date", fields.Datetime.now() + timedelta(seconds=30)
)
return super()._message_update_content(*args, **kwargs)

View File

@ -0,0 +1,15 @@
You need to do nothing. The module is configured appropriately out of the box.
The mail queue processing is made by a cron job. This is normal Odoo behavior,
not specific to this module. However, since you will start using that queue for
every message posted by any user in any thread, this module configures that job
to execute every minute by default.
You can still change that cadence after installing the module (although it is
not recommended). To do so:
#. Log in with an administrator user.
#. Activate developer mode.
#. Go to *Settings > Technical > Automation > Scheduled Actions*.
#. Edit the action named "Mail: Email Queue Manager".
#. Lower down the frequency in the field *Execute Every*. Recommended: 1 minute.

View File

@ -0,0 +1 @@
* Jairo Llopis (https://www.moduon.team/)

View File

@ -0,0 +1,9 @@
This module enhances mail threads by using the mail queue by default.
Without this module, Odoo attempts to notify recipients of your message immediately.
If your mail server is slow or you have many followers, this can mean a lot of time.
Install this module and make Odoo more snappy!
All emails will be kept in the outgoing queue by at least 30 seconds,
giving you some time to re-think what you wrote. During that time,
you can still delete the message and start again.

View File

@ -0,0 +1,3 @@
* Add minimal deferring time configuration if it ever becomes necessary. See
https://github.com/OCA/social/pull/1001#issuecomment-1461581573 for the
rationale behind current hardcoded value of 30 seconds.

View File

@ -0,0 +1,15 @@
To use this module, you need to:
#. Go to the form view of any record that has a mail thread. It can be a partner, for example.
#. Post a message.
The mail is now in the outgoing mail queue. It will be there for at least 30
seconds. It will be really sent the next time the "Mail: Email Queue Manager"
cron job is executed.
While the message has not been yet sent:
#. Hover over the little envelope. You will see a paper airplane icon,
indicating it is still outgoing.
#. Hover over the message and click on the little trash icon to delete it.
Mails will not be sent.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,479 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Deferred Message Posting</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="deferred-message-posting">
<h1 class="title">Deferred Message Posting</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/social/tree/15.0/mail_post_defer"><img alt="OCA/social" src="https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/social-15-0/social-15-0-mail_post_defer"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/205/15.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module enhances mail threads by using the mail queue by default.</p>
<p>Without this module, Odoo attempts to notify recipients of your message immediately.
If your mail server is slow or you have many followers, this can mean a lot of time.
Install this module and make Odoo more snappy!</p>
<p>All emails will be kept in the outgoing queue by at least 30 seconds,
giving you some time to re-think what you wrote. During that time,
you can still delete the message and start again.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>You need to do nothing. The module is configured appropriately out of the box.</p>
<p>The mail queue processing is made by a cron job. This is normal Odoo behavior,
not specific to this module. However, since you will start using that queue for
every message posted by any user in any thread, this module configures that job
to execute every minute by default.</p>
<p>You can still change that cadence after installing the module (although it is
not recommended). To do so:</p>
<ol class="arabic simple">
<li>Log in with an administrator user.</li>
<li>Activate developer mode.</li>
<li>Go to <em>Settings &gt; Technical &gt; Automation &gt; Scheduled Actions</em>.</li>
<li>Edit the action named “Mail: Email Queue Manager”.</li>
<li>Lower down the frequency in the field <em>Execute Every</em>. Recommended: 1 minute.</li>
</ol>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p>To use this module, you need to:</p>
<ol class="arabic simple">
<li>Go to the form view of any record that has a mail thread. It can be a partner, for example.</li>
<li>Post a message.</li>
</ol>
<p>The mail is now in the outgoing mail queue. It will be there for at least 30
seconds. It will be really sent the next time the “Mail: Email Queue Manager”
cron job is executed.</p>
<p>While the message has not been yet sent:</p>
<ol class="arabic simple">
<li>Hover over the little envelope. You will see a paper airplane icon,
indicating it is still outgoing.</li>
<li>Hover over the message and click on the little trash icon to delete it.
Mails will not be sent.</li>
</ol>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Add minimal deferring time configuration if it ever becomes necessary. See
<a class="reference external" href="https://github.com/OCA/social/pull/1001#issuecomment-1461581573">https://github.com/OCA/social/pull/1001#issuecomment-1461581573</a> for the
rationale behind current hardcoded value of 30 seconds.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/social/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_post_defer%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>Moduon</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
<ul class="simple">
<li>Jairo Llopis (<a class="reference external" href="https://www.moduon.team/">https://www.moduon.team/</a>)</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/Yajo"><img alt="Yajo" src="https://github.com/Yajo.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/15.0/mail_post_defer">OCA/social</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,51 @@
/** @odoo-module **/
import {registerPatch} from "@mail/model/model_core";
// Ensure that the model definition is loaded before the patch
import "@mail/models/message";
registerPatch({
name: "Message",
fields: {
canBeDeleted: {
/**
* Whether this message can be updated.
*
* Despite the field name, this method is used upstream to determine
* whether a message can be edited or deleted.
*
* Upstream Odoo allows updating notes. We want to allow updating any
* user message that is not yet sent. If there's a race condition,
* anyways the server will repeat these checks.
*
* @returns {Boolean}
* Whether this message can be updated.
* @override
*/
compute() {
// If upstream allows editing, we are done
if (this._super()) {
return true;
}
// Repeat upstream checks that are still valid for us
if (this.trackingValues.length > 0) {
return false;
}
if (this.message_type !== "comment") {
return false;
}
if (this.originThread.model === "mail.channel") {
return true;
}
// Check that no notification has been sent yet
if (
this.notifications.some((ntf) => ntf.notification_status === "sent")
) {
return false;
}
return true;
},
},
},
});

View File

@ -0,0 +1,2 @@
from . import test_install
from . import test_mail

View File

@ -0,0 +1,12 @@
# Copyright 2022-2023 Moduon Team S.L. <info@moduon.team>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
from odoo.tests.common import TransactionCase
class InstallationCase(TransactionCase):
def test_cron_cadence(self):
"""Test that the post_init_hook was properly executed."""
cron = self.env.ref("mail.ir_cron_mail_scheduler_action")
cadence = cron.interval_number, cron.interval_type
self.assertEqual(cadence, (1, "minutes"))

View File

@ -0,0 +1,180 @@
# Copyright 2022-2023 Moduon Team S.L. <info@moduon.team>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
import freezegun
from odoo.exceptions import UserError
from odoo.addons.mail.tests.common import MailCommon
@freezegun.freeze_time("2023-01-02 10:00:00")
class MessagePostCase(MailCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls._create_portal_user()
# Notify employee by email
cls.user_employee.notification_type = "email"
def test_standard(self):
"""A normal call just uses the queue by default."""
with self.mock_mail_gateway():
msg = self.partner_portal.message_post(
body="test body",
subject="test subject",
message_type="comment",
partner_ids=self.partner_employee.ids,
)
schedules = self.env["mail.message.schedule"].search(
[
("mail_message_id", "=", msg.id),
("scheduled_datetime", "=", "2023-01-02 10:00:30"),
]
)
self.assertEqual(len(schedules), 1)
self.assertNoMail(self.partner_employee)
def test_forced_arg(self):
"""A forced send via method argument is sent directly."""
with self.mock_mail_gateway():
self.partner_portal.message_post(
body="test body",
subject="test subject",
message_type="comment",
partner_ids=self.partner_employee.ids,
force_send=True,
)
self.assertMailMail(
self.partner_employee,
"sent",
author=self.env.user.partner_id,
content="test body",
fields_values={"scheduled_date": False},
)
def test_forced_context(self):
"""A forced send via context is sent directly."""
with self.mock_mail_gateway():
self.partner_portal.with_context(mail_notify_force_send=True).message_post(
body="test body",
subject="test subject",
message_type="comment",
partner_ids=self.partner_employee.ids,
)
self.assertMailMail(
self.partner_employee,
"sent",
author=self.env.user.partner_id,
content="test body",
fields_values={"scheduled_date": False},
)
def test_msg_edit(self):
"""Can update messages.
Upstream Odoo allows only updating notes, regardless of their sent
status. We allow updating any message that is not sent yet.
"""
with self.mock_mail_gateway():
msg = self.partner_portal.message_post(
body="test body",
subject="test subject",
message_type="comment",
partner_ids=self.partner_employee.ids,
subtype_xmlid="mail.mt_comment",
)
schedules = self.env["mail.message.schedule"].search(
[
("mail_message_id", "=", msg.id),
("scheduled_datetime", "=", "2023-01-02 10:00:30"),
]
)
self.assertEqual(len(schedules), 1)
self.assertNoMail(self.partner_employee)
# After 15 seconds, the user updates the message
with freezegun.freeze_time("2023-01-02 10:00:15"):
self.partner_portal._message_update_content(msg, "new body")
schedules = self.env["mail.message.schedule"].search(
[
("mail_message_id", "=", msg.id),
("scheduled_datetime", "=", "2023-01-02 10:00:45"),
]
)
self.assertEqual(len(schedules), 1)
self.assertNoMail(self.partner_employee)
# After a minute, the mail is created
with freezegun.freeze_time("2023-01-02 10:01:00"):
self.env["mail.message.schedule"]._send_notifications_cron()
self.assertMailMail(
self.partner_employee,
"outgoing",
author=self.env.user.partner_id,
content="new body",
)
def test_queued_msg_delete(self):
"""A user can delete a message before it's sent."""
with self.mock_mail_gateway():
msg = self.partner_portal.message_post(
body="test body",
subject="test subject",
message_type="comment",
partner_ids=self.partner_employee.ids,
subtype_xmlid="mail.mt_comment",
)
schedules = self.env["mail.message.schedule"].search(
[
("mail_message_id", "=", msg.id),
("scheduled_datetime", "=", "2023-01-02 10:00:30"),
]
)
self.assertEqual(len(schedules), 1)
# Emulate user clicking on delete button and going through the
# `/mail/message/update_content` controller
self.partner_portal._message_update_content(msg, "", [])
self.env.flush_all()
self.assertFalse(schedules.exists())
self.assertNoMail(
self.partner_employee,
author=self.env.user.partner_id,
)
# One minute later, the cron has no mails to send
with freezegun.freeze_time("2023-01-02 10:01:00"):
self.env["mail.message.schedule"]._send_notifications_cron()
self.env["mail.mail"].process_email_queue()
self.assertNoMail(
self.partner_employee,
author=self.env.user.partner_id,
)
def test_no_sent_msg_delete(self):
"""A user cannot delete a message after it's sent.
Usually, the trash button will be hidden in UI if the message is sent.
However, the server-side protection is still important, because there
can be a race condition when the mail is sent in the background but
the user didn't refresh the view.
"""
with self.mock_mail_gateway():
msg = self.partner_portal.message_post(
body="test body",
subject="test subject",
message_type="comment",
partner_ids=self.partner_employee.ids,
subtype_xmlid="mail.mt_comment",
)
# One minute later, the cron sends the mail
with freezegun.freeze_time("2023-01-02 10:01:00"):
self.env["mail.message.schedule"]._send_notifications_cron()
self.env["mail.mail"].process_email_queue()
self.assertMailMail(
self.partner_employee,
"sent",
author=self.env.user.partner_id,
content="test body",
)
# Emulate user clicking on delete button and going through the
# `/mail/message/update_content` controller
with self.assertRaises(UserError):
self.partner_portal._message_update_content(msg, "", [])

View File

@ -0,0 +1 @@
../../../../mail_post_defer

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)