[11.0][ADD] mail_private

pull/487/head
Enric Tobella 2020-01-16 22:07:10 +01:00
parent 923debe5d9
commit d7830f6253
34 changed files with 1409 additions and 0 deletions

View File

@ -0,0 +1,74 @@
============
Mail Private
============
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
:target: https://github.com/OCA/social/tree/11.0/mail_private
:alt: OCA/social
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/social-11-0/social-11-0-mail_private
: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/11.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This addon allows to define private groups on models.
Then, you can define private messages only visible for members of the groups
**Table of contents**
.. contents::
:local:
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_private%0Aversion:%2011.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
~~~~~~~
* Creu Blanca
Contributors
~~~~~~~~~~~~
* Enric Tobella <etobella@creublanca.es>
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.
This module is part of the `OCA/social <https://github.com/OCA/social/tree/11.0/mail_private>`_ 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 . import wizards

View File

@ -0,0 +1,28 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Mail Private',
'summary': """
Create private emails""",
'version': '11.0.1.0.0',
'license': 'AGPL-3',
'author': 'Creu Blanca,Odoo Community Association (OCA)',
'website': 'https://github.com/OCA/social',
'depends': [
'mail',
],
'qweb': [
'static/src/xml/thread.xml',
'static/src/xml/composer.xml',
],
'data': [
'security/ir.model.access.csv',
'views/mail_security_group.xml',
'wizards/mail_compose_message.xml',
'security/security.xml',
'views/ir_model.xml',
'views/assets.xml',
'views/mail_message.xml',
],
}

View File

@ -0,0 +1,8 @@
from . import mail_message
from . import mail_thread
from . import ir_model
from . import res_partner
from . import mail_channel
from . import res_users
from . import ir_attachment
from . import mail_security_group

View File

@ -0,0 +1,12 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class IrAttachment(models.Model):
_inherit = 'ir.attachment'
mail_group_id = fields.Many2one(
'mail.security.group'
)

View File

@ -0,0 +1,15 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class IrModel(models.Model):
_inherit = 'ir.model'
private_group_ids = fields.Many2many(
'res.groups',
)
mail_group_ids = fields.Many2many(
'mail.security.group'
)

View File

@ -0,0 +1,14 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
class MailChannel(models.Model):
_inherit = 'mail.channel'
@api.multi
def _notify(self, message):
if message.mail_group_id:
return
return super()._notify(message)

View File

@ -0,0 +1,21 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class MailMessage(models.Model):
_inherit = 'mail.message'
mail_group_id = fields.Many2one('mail.security.group', readonly=False)
@api.model
def _message_read_dict_postprocess(self, messages, message_tree):
result = super()._message_read_dict_postprocess(messages, message_tree)
for message_dict in messages:
message_id = message_dict.get('id')
message = message_tree[message_id]
message_dict['private'] = bool(message.mail_group_id)
message_dict['mail_group_id'] = message.mail_group_id.id
message_dict['mail_group'] = message.mail_group_id.name
return result

View File

@ -0,0 +1,37 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class MailSecurityGroup(models.Model):
_name = 'mail.security.group'
_description = 'Mail Security Group'
name = fields.Char(required=True)
model_ids = fields.Many2many(
'ir.model'
)
group_ids = fields.Many2many(
'res.groups'
)
button_name = fields.Char()
icon = fields.Char()
active = fields.Boolean(
default=True
)
def _get_security_groups(self):
vals = []
for record in self:
vals.append(record._get_security_group())
return vals
def _get_security_group(self):
return {
'id': self.id,
'name': self.name,
'button_name': self.button_name or self.name,
'icon': self.icon,
}

View File

@ -0,0 +1,93 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from lxml import etree
from odoo import api, fields, models
from odoo.osv.orm import setup_modifiers
class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
allow_private = fields.Boolean(
compute='_compute_allow_private',
)
def _compute_allow_private(self):
groups = self.env['mail.security.group'].search([
('model_ids.model', '=', self._name),
])
users = groups.mapped('group_ids.users')
for record in self:
record.allow_private = groups and self.env.user in users
@api.model
def get_message_security_groups(self):
return self.env['mail.security.group'].search([
('model_ids.model', '=', self._name),
('group_ids.users', '=', self.env.user.id)
])._get_security_groups()
@api.model
def fields_view_get(
self, view_id=None, view_type="form", toolbar=False, submenu=False
):
res = super().fields_view_get(
view_id=view_id,
view_type=view_type,
toolbar=toolbar,
submenu=submenu,
)
if view_type == 'form':
doc = etree.XML(res['arch'])
for node in doc.xpath("//field[@name='message_ids']/.."):
element = etree.Element(
'field',
attrib={
"name": "allow_private",
"invisible": "1",
}
)
setup_modifiers(element)
node.insert(0, element)
res['arch'] = etree.tostring(doc, encoding='unicode')
return res
def _message_post_process_attachments(
self, attachments, attachment_ids, message_data
):
if attachment_ids and self.env.context.get('default_mail_group_id'):
filtered_attachment_ids = self.env['ir.attachment'].sudo().search([
('res_model', '=', 'mail.compose.message'),
('create_uid', '=', self._uid),
('id', 'in', attachment_ids),
])
filtered_attachment_ids.write({
'mail_group_id': self.env.context.get('default_mail_group_id'),
})
return super()._message_post_process_attachments(
attachments, attachment_ids, message_data)
def message_get_message_notify_values(self, message, message_values):
if hasattr(super(), 'message_get_message_notify_values'):
message_vals = super().message_get_message_notify_values(
message, message_values)
else:
message_vals = message_values.copy()
if not message.mail_group_id:
return message_vals
partner_obj = self.env['res.partner']
accepted_users = message.mail_group_id.mapped(
'group_ids.users')
new_partners = partner_obj.browse()
for act, ext, pids in message_values.get('needaction_partner_ids', []):
if act != 6:
continue
partners = partner_obj.browse(pids)
for partner in partners:
if partner.user_ids in accepted_users:
new_partners |= partner
new_vals = {}
if new_partners:
new_vals['needaction_partner_ids'] = [(6, 0, new_partners.ids)]
return new_vals

View File

@ -0,0 +1,43 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
class ResPartner(models.Model):
_inherit = 'res.partner'
@api.multi
def _notify(self, message, force_send=False, send_after_commit=True,
user_signature=True):
if not message.mail_group_id:
return super(ResPartner, self)._notify(
message, force_send=force_send,
send_after_commit=send_after_commit,
user_signature=user_signature
)
accepted_users = message.mail_group_id.mapped(
'group_ids.users')
partners = self.browse()
for partner in self:
if partner.user_ids in accepted_users:
partners |= partner
return super(ResPartner, partners)._notify(
message, force_send=force_send,
send_after_commit=send_after_commit, user_signature=user_signature
)
@api.multi
def _notify_by_chat(self, message):
if not message.mail_group_id:
return super()._notify_by_chat(message)
accepted_users = message.mail_group_id.mapped(
'group_ids.users')
partners = self.browse()
for partner in self:
if partner.user_ids in accepted_users:
partners |= partner
elif partner.user_ids:
message.sudo(partner.user_ids[0]).set_message_done()
return super(ResPartner, partners)._notify_by_chat(message)

View File

@ -0,0 +1,20 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class ResUsers(models.Model):
_inherit = 'res.users'
message_group_ids = fields.Many2many(
'mail.security.group',
compute='_compute_message_group_ids'
)
@api.depends('groups_id')
def _compute_message_group_ids(self):
for record in self:
record.message_group_ids = self.env['mail.security.group'].search([
('group_ids', 'in', record.groups_id.ids)
])

View File

@ -0,0 +1,4 @@
* Access in developer mode
* Access to `Settings > Technical > Email > Mail Group`
* Create a new group from on a model that has a thread and specify some
security groups

View File

@ -0,0 +1 @@
* Enric Tobella <etobella@creublanca.es>

View File

@ -0,0 +1,3 @@
This addon allows to define private groups on models.
Then, you can define private messages only visible for members of message groups.
Each model may have one or more visible groups.

View File

@ -0,0 +1,5 @@
* Access a record from a model with at least one mail group defined
* Try to send a message
* A new button will be added that allows to set the message as private.
There will be as many buttons as groups you can use.
* The message will only be visible by users that are on the group.

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_group_all,mail.security.group.all,model_mail_security_group,,1,0,0,0
manage_mail_message_group_all,mail.security.group.manage,model_mail_security_group,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mail_message_group_all mail.security.group.all model_mail_security_group 1 0 0 0
3 manage_mail_message_group_all mail.security.group.manage model_mail_security_group base.group_system 1 1 1 1

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="message_private_rule" model="ir.rule">
<field name="model_id" ref="mail.model_mail_message"/>
<field name="name">Message private rules</field>
<field name="domain_force">['|', '|', ('mail_group_id', '=', False), ('model', '=', False), ('mail_group_id', 'in', user.message_group_ids.ids)]</field>
</record>
<record id="attachment_private_rule" model="ir.rule">
<field name="model_id" ref="base.model_ir_attachment"/>
<field name="name">Attachment private rules</field>
<field name="domain_force">['|', '|', ('mail_group_id', '=', False), ('res_model', '=', False), ('mail_group_id', 'in', user.message_group_ids.ids)]</field>
</record>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,420 @@
<?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.14: http://docutils.sourceforge.net/" />
<title>Mail Private</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="mail-private">
<h1 class="title">Mail Private</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="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/social/tree/11.0/mail_private"><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-11-0/social-11-0-mail_private"><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/11.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This addon allows to define private groups on models.
Then, you can define private messages only visible for members of the groups</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#bug-tracker" id="id1">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id2">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id3">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id4">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id5">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id1">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_private%0Aversion:%2011.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="#id2">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id3">Authors</a></h2>
<ul class="simple">
<li>Creu Blanca</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id4">Contributors</a></h2>
<ul class="simple">
<li>Enric Tobella &lt;<a class="reference external" href="mailto:etobella&#64;creublanca.es">etobella&#64;creublanca.es</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id5">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>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/11.0/mail_private">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,76 @@
/* Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
odoo.define('mail_private.composer', function (require) {
"use strict";
var ChatterComposer = require('mail.ChatterComposer');
ChatterComposer.include({
init: function (parent, model, suggested_partners, options) {
this._super(parent, model, suggested_partners, options);
_.extend(this.events, {
'click .o_composer_button_send_private': 'on_send_private',
});
this.options.allow_private =
parent.record.data.allow_private || false;
},
renderElement: function () {
var result = this._super.apply(this, arguments);
if (this.options.allow_private) {
var self = this;
this._rpc({
model: this.model,
method: 'get_message_security_groups',
args: [],
}).then(function (data) {
self.security_groups = data;
self._update_security_groups();
});
}
return result;
},
_get_group_button: function (group) {
var $button = $('<button>', {
'class': 'o_dropdown_toggler_btn btn btn-sm ' +
'o_composer_button_send_private hidden-xs',
'type': 'button',
'data-group-id': group.id,
});
if (group.icon) {
var $icon = $('<i>', {
'class': 'o_thread_private_tooltip ' +
'o_thread_message_private fa fa-lg ' + group.icon,
});
$button.append($icon);
} else {
var $data = $('<span>');
$data.text(group.button_name);
$button.append($data);
}
return $button;
},
_update_security_groups: function () {
var node = this.$el.find('.o_composer_send_private_group');
var self = this;
_.each(this.security_groups, function (group) {
var $button = self._get_group_button(group);
node.append($button);
});
},
on_send_private: function (event) {
if (this.is_empty() || !this.do_check_attachment_upload()) {
return;
}
var group_id = event.currentTarget.getAttribute('data-group-id');
clearTimeout(this.canned_timeout);
var self = this;
this.preprocess_message().then(function (message) {
message.context.default_mail_group_id = group_id;
self.trigger('post_message', message);
self.clear_composer_on_send();
self.$input.focus();
});
},
});
});

View File

@ -0,0 +1,17 @@
/* Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
odoo.define('mail_private.make_message', function (require) {
"use strict";
var chat_manager = require('mail.chat_manager');
chat_manager._make_message_super_private = chat_manager.make_message;
chat_manager.make_message = function (data) {
var msg = this._make_message_super_private(data);
msg.private = data.private || false;
msg.mail_group_id = data.mail_group_id || false;
msg.mail_group = data.mail_group || false;
return msg;
};
});

View File

@ -0,0 +1,18 @@
.o_thread_tooltip_private_container {
.o_security_group_overlay {
display: none;
position: absolute;
background: @gray-lighter;
padding: 4px;
border: solid @gray-lighter 1px;
border-radius: 5px;
opacity: 0;
transition: opacity 0.5s;
overflow: hidden;
z-index: 20;
}
&:hover .o_security_group_overlay {
display: inline;
opacity: 1;
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<div t-extend="mail.chatter.ChatComposer">
<t t-jquery=".o_composer_send" t-operation="append">
<div class="btn-group o_composer_send_private_group"/>
</t>
</div>
</templates>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-extend="mail.ChatThread.Message">
<t t-jquery=".o_thread_tooltip_container" t-operation="after">
<span t-if="message.private" class="o_thread_tooltip_private_container">
<i t-att-class="'o_thread_private_tooltip o_thread_message_private fa fa-user-secret fa-lg'"/>
<span class="o_security_group_overlay">
<t t-esc="message.mail_group"/>
</span>
</span>
</t>
</t>
</templates>

View File

@ -0,0 +1 @@
from . import test_mail_private

View File

@ -0,0 +1,281 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import base64
from odoo.tools import formataddr
from odoo.addons.mail.tests.common import TestMail
from odoo.tests.common import post_install, at_install
from lxml import etree
@at_install(False)
@post_install(True)
class TestMailPrivate(TestMail):
@classmethod
def setUpClass(cls):
super(TestMailPrivate, cls).setUpClass()
self = cls
self.user_01 = self.env['res.users'].create({
'name': 'user_01',
'login': 'demo_user_01',
'email': 'demo@demo.de',
'notification_type': 'inbox',
})
self.user_02 = self.env['res.users'].create({
'name': 'user_02',
'login': 'demo_user_02',
'email': 'demo2@demo.de',
'notification_type': 'inbox',
})
self.user_03 = self.env['res.users'].create({
'name': 'user_03',
'login': 'demo_user_03',
'email': 'demo3@demo.de',
'notification_type': 'inbox',
})
self.partner = self.env['res.partner'].create({
'name': 'Partner',
'customer': True,
'email': 'test@test.com',
})
self.group_1 = self.env['res.groups'].create({
'name': 'DEMO GROUP 1',
'users': [(4, self.user_01.id)],
})
self.group_2 = self.env['res.groups'].create({
'name': 'DEMO GROUP 2',
'users': [(4, self.user_02.id)],
})
self.message_group_1 = self.env['mail.security.group'].create({
'name': 'GROUP 1',
'model_ids': [(4, self.env.ref('base.model_res_partner').id)],
'group_ids': [(4, self.group_1.id)],
})
self.message_group_2 = self.env['mail.security.group'].create({
'name': 'GROUP 2',
'model_ids': [(4, self.env.ref('base.model_res_partner').id)],
'group_ids': [(4, self.group_2.id)],
})
self.subtypes, _ = self.env[
'mail.message.subtype'
].auto_subscribe_subtypes(self.partner._name)
self.partner.message_subscribe(
partner_ids=self.user_01.partner_id.ids,
subtype_ids=self.subtypes.ids,
)
self.partner.message_subscribe(
partner_ids=self.user_02.partner_id.ids,
subtype_ids=self.subtypes.ids,
)
self.partner.message_subscribe(
partner_ids=self.user_03.partner_id.ids,
subtype_ids=self.subtypes.ids,
)
def _get_notifications(self, message, user):
return self.env['mail.notification'].search([
('mail_message_id', '=', message.id),
('res_partner_id', '=', user.partner_id.id),
('is_read', '=', False),
])
def test_normal_usage(self):
# pylint: disable: C8107
message = self.partner.message_post(body="DEMO_01")
self.assertTrue(
self._get_notifications(message, self.user_01)
)
self.assertTrue(
self._get_notifications(message, self.user_02)
)
self.assertTrue(
self._get_notifications(message, self.user_03)
)
def test_private_usage(self):
# pylint: disable: C8107
message = self.partner.sudo(self.user_01.id).with_context(
default_mail_group_id=self.message_group_1.id
).message_post(body="DEMO_01")
self.assertFalse(
self._get_notifications(message, self.user_02)
)
self.assertFalse(
self.env['mail.message'].sudo(self.user_02.id).search([
('id', '=', message.id)
])
)
self.assertFalse(
self._get_notifications(message, self.user_03)
)
def test_private_message_data(self):
# pylint: disable: C8107
message = self.partner.with_context(
default_mail_group_id=self.message_group_1.id
).message_post(body="DEMO_01")
self.assertTrue(message.message_format()[0]['private'])
def test_message_data(self):
# pylint: disable: C8107
message = self.partner.message_post(body="DEMO_01")
self.assertFalse(message.message_format()[0]['private'])
def test_private_notification(self):
# pylint: disable: C8107
message = self.partner.with_context(
default_mail_group_id=self.message_group_1.id
).message_post(body="DEMO_01")
self.assertTrue(
self._get_notifications(message, self.user_01)
)
self.assertEqual(
self.env['mail.message'].sudo(self.user_01.id).search([
('id', '=', message.id)
]), message
)
self.assertFalse(
self._get_notifications(message, self.user_02)
)
self.assertFalse(
self.env['mail.message'].sudo(self.user_02.id).search([
('id', '=', message.id)
])
)
self.assertFalse(
self._get_notifications(message, self.user_03)
)
def test_attachment(self):
attachment = self.env['ir.attachment'].sudo(self.user_01.id).create({
'datas': base64.b64encode("TXT DATA".encode("utf-8")),
'name': 'demo_file.txt',
'res_model': 'mail.compose.message',
})
# pylint: disable: C8107
message = self.partner.sudo(self.user_01.id).message_post(
body="DEMO_01", attachment_ids=attachment.ids)
self.assertFalse(attachment.mail_group_id)
self.assertFalse(message.mail_group_id)
self.assertIn(attachment, message.attachment_ids)
def test_attachment_private(self):
attachment = self.env['ir.attachment'].sudo(self.user_01.id).create({
'datas': base64.b64encode("TXT DATA".encode("utf-8")),
'name': 'demo_file.txt',
'res_model': 'mail.compose.message',
})
# pylint: disable: C8107
message = self.partner.sudo(self.user_01.id).with_context(
default_mail_group_id=self.message_group_1.id
).message_post(body="DEMO_01", attachment_ids=attachment.ids)
self.assertEqual(self.message_group_1, attachment.mail_group_id)
self.assertEqual(self.message_group_1, message.mail_group_id)
self.assertIn(attachment, message.attachment_ids)
def test_allow_private(self):
self.assertTrue(
self.partner.sudo(self.user_01.id).allow_private
)
self.assertTrue(
self.partner.sudo(self.user_02.id).allow_private
)
view = self.partner.fields_view_get(view_type="form")
view_element = etree.XML(view['arch'])
self.assertTrue(view_element.xpath("//field[@name='allow_private']"))
def test_compose_message_private(self):
current_messages = self.partner.message_ids
compose = self.env['mail.compose.message'].with_context({
'default_composition_mode': 'comment',
'default_model': self.partner._name,
'default_res_id': self.partner.id
}).sudo(self.user_01).create({
'subject': 'Subject',
'body': 'Body text',
'partner_ids': []})
self.assertTrue(compose.allow_private)
compose.mail_group_id = self.message_group_1
compose.send_mail()
message = self.partner.message_ids - current_messages
self.assertTrue(message)
self.assertEqual(message.mail_group_id, self.message_group_1)
def test_compose_message_public(self):
current_messages = self.partner.message_ids
compose = self.env['mail.compose.message'].with_context({
'default_composition_mode': 'comment',
'default_model': self.partner._name,
'default_res_id': self.partner.id
}).sudo(self.user_01).create({
'subject': 'Subject',
'body': 'Body text',
'partner_ids': []})
self.assertTrue(compose.allow_private)
compose.send_mail()
message = self.partner.message_ids - current_messages
self.assertTrue(message)
self.assertFalse(message.mail_group_id)
def test_compose_message_compute(self):
compose = self.env['mail.compose.message'].with_context({
'default_composition_mode': 'comment',
'default_model': self.partner._name,
'default_res_id': self.partner.id
}).sudo(self.user_01.id).create({
'subject': 'Subject',
'body': 'Body text',
'partner_ids': []})
self.assertTrue(compose.allow_private)
compose = self.env['mail.compose.message'].with_context({
'default_composition_mode': 'comment',
'default_model': self.partner._name,
'default_res_id': self.partner.id
}).sudo(self.user_03.id).create({
'subject': 'Subject',
'body': 'Body text',
'partner_ids': []})
self.assertFalse(compose.allow_private)
def test_security_groups(self):
groups = self.partner.sudo(
self.user_01.id).get_message_security_groups()
self.assertTrue(groups)
self.assertEqual(1, len(groups))
self.assertEqual(groups[0]['id'], self.message_group_1.id)
groups = self.partner.sudo(
self.user_02.id).get_message_security_groups()
self.assertTrue(groups)
self.assertEqual(1, len(groups))
self.assertEqual(groups[0]['id'], self.message_group_2.id)
groups = self.partner.sudo(
self.user_03.id).get_message_security_groups()
self.assertFalse(groups)
def test_email_sending(self):
self.assertFalse(self._mails)
self.user_01.write({'notification_type': 'email'})
self.user_02.write({'notification_type': 'email'})
# pylint: disable: C8107
message = self.partner.with_context(
default_mail_group_id=self.message_group_1.id
).message_post(body="DEMO_01")
self.assertTrue(message.mail_group_id)
self.assertFalse(self._get_notifications(message, self.user_01))
self.assertFalse(self.env['mail.mail'].search([
('mail_message_id', '=', message.id)
]))
self.assertTrue(self._mails)
self.assertEqual(1, len(self._mails))
partner = self.user_01.partner_id
email_to = [formataddr((partner.name or 'False', partner.email or 'False'))]
self.assertEqual(
self._mails[0]['email_to'],
email_to)
self.assertFalse(
self._get_notifications(message, self.user_02)
)
self.assertFalse(
self._get_notifications(message, self.user_03)
)

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo>
<template id="assets_backend"
name="mail_private assets"
inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/less"
href="/mail_private/static/src/less/message.less"/>
<script type="text/javascript"
src="/mail_private/static/src/js/message.js"/>
<script type="text/javascript"
src="/mail_private/static/src/js/composer.js"/>
</xpath>
</template>
</odoo>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="model_form_view">
<field name="name">ir.model.form (in mail_private)</field>
<field name="model">ir.model</field>
<field name="inherit_id" ref="mail.model_form_view"/>
<field name="arch" type="xml">
<field name="is_mail_thread" position="after">
<field name="mail_group_ids"
widget="many2many_tags"
attrs="{'invisible': [('is_mail_thread', '=', False)]}"/>
</field>
</field>
</record>
</odoo>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="view_message_form">
<field name="name">mail.message.form (in mail_private)</field>
<field name="model">mail.message</field>
<field name="inherit_id" ref="mail.view_message_form"/>
<field name="arch" type="xml">
<field name="subtype_id" position="after">
<field name="mail_group_id"/>
</field>
</field>
</record>
</odoo>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="mail_security_group_form_view">
<field name="name">mail.security.group.form (in mail_private)</field>
<field name="model">mail.security.group</field>
<field name="arch" type="xml">
<form>
<header/>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
</button>
</div>
<group>
<field name="name"/>
<field name="model_ids" widget="many2many_tags"/>
<field name="group_ids" widget="many2many_tags"/>
<field name="icon"/>
<field name="button_name"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="mail_security_group_search_view">
<field name="name">mail.security.group.search (in mail_private)</field>
<field name="model">mail.security.group</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
</search>
</field>
</record>
<record model="ir.ui.view" id="mail_security_group_tree_view">
<field name="name">mail.security.group.tree (in mail_private)</field>
<field name="model">mail.security.group</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="mail_security_group_act_window">
<field name="name">Mail Group</field>
<field name="res_model">mail.security.group</field>
<field name="view_mode">tree,form</field>
<field name="domain">[]</field>
<field name="context">{}</field>
</record>
<record model="ir.ui.menu" id="mail_security_group_menu">
<field name="name">Mail Group</field>
<field name="parent_id" ref="base.menu_email"/>
<field name="action" ref="mail_security_group_act_window"/>
<field name="sequence" eval="16"/>
</record>
</odoo>

View File

@ -0,0 +1 @@
from . import mail_compose_message

View File

@ -0,0 +1,27 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class MailComposeMessage(models.TransientModel):
_inherit = 'mail.compose.message'
allow_private = fields.Boolean(
compute='_compute_allow_private',
compute_sudo=True,
)
@api.depends('model', 'res_id')
def _compute_allow_private(self):
for record in self:
record.allow_private = record.model and record.res_id and self.env[
record.model
].browse(record.res_id).allow_private
def get_mail_values(self, res_ids):
res = super().get_mail_values(res_ids)
for r in res:
res[r]['mail_group_id'] = self.mail_group_id.id
return res

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="email_compose_message_wizard_form">
<field name="name">mail.compose.message.form (in mail_private)</field>
<field name="model">mail.compose.message</field>
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
<field name="arch" type="xml">
<field name="subject" position="after">
<field name="mail_group_id"
attrs="{'invisible': [('allow_private', '=', False)]}"
domain="[('model_ids.model', '=', model), ('group_ids.users', '=', uid)]" widget="selection"/>
<field name="allow_private" invisible="1"/>
</field>
</field>
</record>
</odoo>