merge work from odoo 15.0

This commit is contained in:
Benoît Vézina 2023-07-22 07:25:40 -04:00
parent b08b383d29
commit d4e377f5c6
46 changed files with 1568 additions and 0 deletions

View file

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

View file

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
{
'name': 'Helpdesk Mailcow Blacklist',
'version': '1.0.0',
'category': 'Administration',
'summary': 'Module for adding blacklist functionality to the helpdesk.',
'description': """
Helpdesk Mailcow Blacklist
This module extends the functionality of the Helpdesk and Mailcow Blacklist modules by adding an action to blacklist the sender of a Helpdesk ticket.
Main Features:
- Adds a button on Helpdesk tickets to blacklist the email sender.
- This button is only visible when an email is associated with the ticket.
- Blacklisting an email sender automatically moves the Helpdesk ticket to a new 'Spam' stage, which is marked as closed.
- The 'Spam' stage is automatically created by this module.
""",
'sequence': 10,
'license': 'GPL-3',
'author': 'Bemade',
'website': 'https://www.bemade.org',
'depends': [
'helpdesk',
'bemade_mailcow_integration'
],
'data': [
# 'security/ir.model.access.csv',
'data/helpdesk_stages.xml',
'views/helpdesk_ticket_views.xml',
],
'demo': [
'demo/demo.xml'
],
'installable': True,
'application': False,
'auto_install': False
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="helpdesk_stage_spam" model="helpdesk.stage">
<field name="name">Spam</field>
<field name="sequence">50</field>
<field name="is_close">True</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<!-- Demo User -->
<record id="demo_user_1" model="res.users">
<field name="name">Demo User</field>
<field name="login">demo_user</field>
<field name="password">demo_user</field>
<field name="email">demo_user@example.com</field>
</record>
<!-- Demo Helpdesk Team -->
<record id="demo_helpdesk_team_1" model="helpdesk.team">
<field name="name">Demo Helpdesk Team</field>
</record>
<!-- Demo Helpdesk Ticket -->
<record id="demo_helpdesk_ticket_1" model="helpdesk.ticket">
<field name="name">Demo Helpdesk Ticket</field>
<field name="team_id" ref="demo_helpdesk_team_1"/>
<field name="user_id" ref="demo_user_1"/>
<field name="partner_email">demo_sender@example.com</field>
<field name="partner_id" ref="base.res_partner_1"/>
<field name="description">This is a demo ticket for testing the blacklist feature.</field>
</record>
</data>
</odoo>

View file

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

View file

@ -0,0 +1,27 @@
from odoo import api, fields, models
from odoo.exceptions import UserError
import re
class HelpdeskTicket(models.Model):
_inherit = 'helpdesk.ticket'
def action_add_blacklist(self):
self.ensure_one()
email_regex = r'<([^<>]+)>'
email_to_blacklist = re.findall(email_regex, self.email)[0]
# Create a new blacklist record
blacklist = self.env['mail.mailcow.blacklist'].create({
'email': email_to_blacklist,
})
# Assign 'spam' as a closed stage
spam_stage = self.env.ref('bemade_helpdesk_mailcow_blacklist.helpdesk_stage_spam')
if spam_stage:
self.stage_id = spam_stage.id
else:
raise UserError(_('The Spam stage does not exist.'))
return True

View file

@ -0,0 +1,11 @@
from odoo import models, api
class ResPartner(models.Model):
_inherit = 'res.partner'
@api.multi
def send_validation_email(self):
for partner in self:
if not partner.email_validated:
# Assuming you have a method called send_validation_email that sends the validation email.
partner.send_validation_email()

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_bemade_helpdesk_mailcow_blacklist,access_bemade_helpdesk_mailcow_blacklist,model_bemade_helpdesk_mailcow_blacklist,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_bemade_helpdesk_mailcow_blacklist access_bemade_helpdesk_mailcow_blacklist model_bemade_helpdesk_mailcow_blacklist base.group_user 1 1 1 1

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_ticket_form_inherit_mailcow" model="ir.ui.view">
<field name="name">helpdesk.ticket.form.mailcow</field>
<field name="model">helpdesk.ticket</field>
<field name="inherit_id" ref="helpdesk.helpdesk_ticket_view_form"/>
<field name="arch" type="xml">
<header position="inside">
<button name="action_add_blacklist" type="object" string="Add to Blacklist" class="oe_highlight" attrs="{'invisible': [('partner_email', '=', False)]}"/>
</header>
</field>
</record>
</odoo>

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

View file

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
{
'name': 'Mailcow Integration',
'version': '1.0.0',
'category': 'Administration',
'summary': 'Module for integrating Mailcow email server with Odoo.',
'description': """
Mailcow Integration
This module integrates the Mailcow email server with Odoo, providing a seamless email communication solution for your Odoo instance. It allows for syncing of mailboxes and email aliases from Mailcow to Odoo and vice versa.
Main Features:
Synchronize Mailcow mailboxes with Odoo users.
Synchronize Mailcow email aliases with Odoo.
Configuration of Mailcow API credentials in Odoo settings.
Automatically create and manage mailboxes and aliases in Mailcow when they are created in Odoo.
""",
'sequence': 10,
'license': 'GPL-3',
'author': 'Bemade',
'website': 'https://www.bemade.org',
'depends': [
'hr',
'mail',
'bemade_user_password_bundle'
],
'data': [
'security/ir.model.access.csv',
'views/res_config_settings_views.xml',
'views/mailcow_mailbox_views.xml',
'views/mailcow_alias_views.xml',
'views/mailcow_blacklist_views.xml',
'views/res_users_views.xml',
],
"assets": {
"web.assets_backend": [
"bemade_mailcow_integration/static/src/js/mailcow.js",
],
"web.assets_qweb": [
"bemade_mailcow_integration/static/src/xml/mailcow_templates.xml",
],
},
'installable': True,
'application': False,
'auto_install': False
}

View file

@ -0,0 +1,13 @@
from odoo import http
from odoo.http import request
class EmailValidationController(http.Controller):
@http.route('/email_validation/<int:partner_id>/<token>', type='http', auth='public', website=True)
def email_validation(self, partner_id, token):
partner = request.env['res.partner'].sudo().browse(partner_id)
if partner and partner.validation_token == token:
partner.sudo().write({'email_validated': True})
return "Email validated!"
else:
return "Invalid validation link."

View file

@ -0,0 +1,30 @@
<odoo>
<data>
<!--
<record id="object0" model="bemade_mailcow_blacklist.bemade_mailcow_blacklist">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model="bemade_mailcow_blacklist.bemade_mailcow_blacklist">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model="bemade_mailcow_blacklist.bemade_mailcow_blacklist">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model="bemade_mailcow_blacklist.bemade_mailcow_blacklist">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model="bemade_mailcow_blacklist.bemade_mailcow_blacklist">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
from . import mail_alias
from . import mailcow
from . import mailcow_alias
from . import mailcow_blacklist
from . import mailcow_mailbox
from . import res_config_settings
from . import res_users

View file

@ -0,0 +1,29 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class MailAlias(models.Model):
_inherit = 'mail.alias'
mailcow_id = fields.One2many('mail.mailcow.alias', 'alias_id')
@api.model
def create(self, vals):
alias = super(MailAlias, self).create(vals)
alias_domain = self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain"),
catchall_alias = self.env["ir.config_parameter"].sudo().get_param("mail.catchall.alias"),
alias_domain = alias_domain[0]
catchall_alias = catchall_alias[0]
if alias_domain:
mailcow_alias = self.env['mail.mailcow.alias'].search([('address', '=', alias.alias_name + '@' + alias_domain)])
if mailcow_alias:
mailcow_alias.write({'active': True})
else:
self.env['mail.mailcow.alias'].create({
'address': alias.alias_name + '@' + alias_domain,
'goto': catchall_alias + '@' + alias_domain,
'alias_id': alias.id,
})
return alias

View file

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
import requests
import logging
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class MailMailcow(models.AbstractModel):
_name = 'mail.mailcow'
_description = 'Mailcow API'
@property
def get_credentials(self):
params = self.env['ir.config_parameter'].sudo()
base_url = params.get_param('mailcow.base_url')
api_key = params.get_param('mailcow.api_key')
if not base_url or not api_key:
_logger.error('No API key or base URL is set in the system parameters')
raise ValidationError(_("No API key or base URL is set in the system parameters. Please set one and try again."))
# return False
else:
return {
'base_url': base_url,
'api_key': api_key
}
def api_request(self, endpoint, method='GET', data=None):
creds = self.get_credentials
if not creds:
return False
url = creds['base_url'] + endpoint
headers = {
'accept': 'application/json',
'X-API-Key': creds['api_key'],
'Content-Type': 'application/json',
}
try:
if method == 'GET':
response = requests.get(url, headers=headers)
elif method == 'POST':
response = requests.post(url, headers=headers, json=data)
elif method == 'DELETE':
response = requests.delete(url, headers=headers)
elif method == 'PUT':
response = requests.put(url, headers=headers, json=data)
response.raise_for_status()
except requests.exceptions.HTTPError as error:
_logger.error('HTTP error occurred: %s', error)
return False
except Exception as error:
_logger.error('An error occurred: %s', error)
return False
return response.json()

View file

@ -0,0 +1,108 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import logging
_logger = logging.getLogger(__name__)
class MailcowAlias(models.Model):
_name = 'mail.mailcow.alias'
_description = 'Mailcow Alias'
_inherit = ['mail.mailcow', 'mail.thread', 'mail.activity.mixin']
_rec_name = 'address'
address = fields.Char(string='Alias Address', required=True, tracking=True)
goto = fields.Char(string='Alias Destination', required=True, tracking=True)
active = fields.Boolean(string='Active', default=True, tracking=True)
catchall = fields.Boolean(string='Catchall', default=False, tracking=True)
alias_id = fields.Many2one(comodel_name='mail.alias', string='Alias')
create_date_mailcow = fields.Datetime(string='Created on Mailcow', readonly=True)
modify_date_mailcow = fields.Datetime(string='Modified on Mailcow', readonly=True)
mc_id = fields.Integer(string='Mailcow ID', readonly=True)
_sql_constraints = [
('address_unique', 'UNIQUE(address)', 'The alias address must be unique!'),
]
@api.model
def create(self, vals):
alias = super(MailcowAlias, self).create(vals)
if 'mc_id' not in vals:
data = {
"active": bool(alias.active),
"address": alias.address,
"catchall": bool(alias.catchall),
"goto": alias.goto,
"private_comment": f"Created by {self.env.user.name} on {fields.Datetime.now()}",
"public_comment": "Alias created in Odoo"
}
result = self.env['mail.mailcow'].api_request('/api/v1/add/alias', 'POST', data)
if not result:
#pass
raise ValidationError("Failed to create alias on Mailcow server.")
return alias
def unlink(self):
for record in self:
endpoint = f"api/v1/delete/alias/{record.mc_id}"
result = self.env['mail.mailcow'].api_request(endpoint, 'POST')
if not result:
raise ValidationError(_("Failed to delete alias on Mailcow server."))
return super(MailcowAlias, self).unlink()
def write(self, vals):
for record in self:
data = {
"attr": {
"active": str(int(vals.get('active', record.active))),
"address": vals.get('address', record.address),
"goto": vals.get('goto', record.goto),
"private_comment": f"Modified by {self.env.user.name} on {fields.Datetime.now()}",
"public_comment": f"Alias modified in Odoo. Changes: {vals}",
},
"items": [record.mc_id]
}
result = self.env['mail.mailcow'].api_request('/api/v1/edit/alias', 'POST', data)
if not result:
raise ValidationError(_("Failed to update alias on Mailcow server."))
return super(MailcowAlias, self).write(vals)
@api.model
def sync_aliases(self):
"""
Synchronize the list of aliases from Mailcow server with Odoo.
For each alias fetched from Mailcow server, it tries to find a matching record
in Odoo. If it doesn't exist, it creates a new record.
"""
endpoint = '/api/v1/get/alias/all'
mailcow_aliases = self.api_request(endpoint)
if not mailcow_aliases:
return
for mc_alias in mailcow_aliases:
domain = mc_alias['domain']
alias_domain = self.env['ir.config_parameter'].sudo().get_param('mail.catchall.domain')
if domain == alias_domain:
alias = self.search([('mc_id', '=', mc_alias['id'])], limit=1)
if not alias:
self.create({
'address': mc_alias['address'],
'active': bool(mc_alias['active']),
'goto': mc_alias['goto'],
'mc_id': mc_alias['id'],
'create_date_mailcow': mc_alias['created'],
'modify_date_mailcow': mc_alias['modified'],
})
else:
alias.write({
'address': mc_alias['address'],
'active': bool(mc_alias['active']),
'goto': mc_alias['goto'],
'create_date_mailcow': mc_alias['created'],
'modify_date_mailcow': mc_alias['modified'],
})

View file

@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
import logging
import json
_logger = logging.getLogger(__name__)
class MailcowBlacklist(models.Model):
_name = 'mail.mailcow.blacklist'
_description = 'Mailcow Blacklist'
_inherit = ['mail.mailcow', 'mail.thread', 'mail.activity.mixin']
email = fields.Char(string='Email', required=True, tracking=True)
mc_id = fields.Integer(string='Mailcow ID', required=True, tracking=True)
@api.model
def create(self, vals):
"""
Overridden create method to add the new blacklist entry to the Mailcow server.
"""
domain = self.env['ir.config_parameter'].sudo().get_param('mail.catchall.domain')
endpoint_add = '/api/v1/add/domain-policy'
endpoint_get_bl = f"/api/v1/get/policy_bl_domain/{domain}"
data = {
'domain': domain,
'object_from': vals['email'],
'object_list': 'bl'
}
self.api_request(endpoint_add, 'POST', data)
_logger.info(f'Added {vals["email"]} to Mailcow blacklist')
bl_emails = self.api_request(endpoint_get_bl, 'GET', None)
mc_id = [d['prefid'] for d in bl_emails if d['value'] == vals['email']]
vals['mc_id'] = mc_id[0]
res = super().create(vals)
return res
def write(self, vals):
"""
Overridden write method to update the blacklist entry on the Mailcow server.
"""
old_email = self.email
res = super().write(vals)
if 'email' in vals:
delete_endpoint = '/api/v1/delete/domain-policy'
add_endpoint = '/api/v1/add/domain-policy'
delete_data = {
'items': [old_email]
}
add_data = {
'items': [vals['email']]
}
self.api_request(delete_endpoint, 'POST', delete_data)
self.api_request(add_endpoint, 'POST', add_data)
_logger.info(f'Updated {old_email} to {vals["email"]} in Mailcow blacklist')
return res
def unlink(self):
"""
Overridden unlink method to remove the blacklist entry from the Mailcow server.
"""
endpoint = '/api/v1/delete/domain-policy'
for record in self:
data = json.dumps(record.mc_id)
self.api_request(endpoint, 'POST', data)
_logger.info(f'Removed {record.email} from Mailcow blacklist')
return super().unlink()
@api.model
def sync_blacklist(self):
"""
Function to sync Mailcow blacklist with Odoo's blacklist. It fetches the list from Mailcow
and updates Odoo's blacklist accordingly.
Returns:
bool: True if the sync is successful, False otherwise
"""
params = self.env['ir.config_parameter'].sudo()
domain = params.get_param('mail.catchall.domain')
endpoint = f'/api/v1/get/policy_bl_domain/{domain}'
try:
response = self.api_request(endpoint, 'GET')
if response:
for item in response:
existing = self.search([('mc_id', '=', item['prefid'])], limit=1)
if existing:
if existing.email != item['value']:
existing.write({'email': item['valuw']})
else:
self.create({
'email': item['value'],
'mc_id': item['prefid'],
})
return True
except Exception as e:
_logger.error('An error occurred while syncing blacklist: %s', str(e))
return False

View file

@ -0,0 +1,143 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import logging
import secrets
import string
_logger = logging.getLogger(__name__)
class MailcowMailbox(models.Model):
_name = 'mail.mailcow.mailbox'
_inherit = ['mail.mailcow', 'mail.thread', 'mail.activity.mixin']
_description = 'Mailcow Mailbox'
def _default_domain(self):
return self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain")
name = fields.Char(tracking=True)
address = fields.Char(compute='_compute_address', store=True, readonly=True, tracking=True)
local_part = fields.Char(required=True, tracking=True)
domain = fields.Char(required=True, tracking=True, default=_default_domain)
active = fields.Boolean(default=True, tracking=True)
user_id = fields.Many2one('res.users', ondelete='cascade', tracking=True)
password = fields.Char(readonly=True, tracking=True)
@api.depends('local_part', 'domain')
def _compute_address(self):
for record in self:
record.address = f"{record.local_part}@{record.domain}"
@api.model
def sync_mailboxes(self):
"""
Synchronize Mailcow mailboxes with Odoo
"""
endpoint = '/api/v1/get/mailbox/all'
data = self.api_request(endpoint)
domain = self.env['ir.config_parameter'].sudo().get_param('mail.catchall.domain')
if data:
for item in data:
if item['domain'] == domain:
mailbox = self.env['mail.mailcow.mailbox'].search([('address', '=', f"{item['local_part']}@{item['domain']}")])
if not mailbox:
self.create({
'name': item['name'],
'local_part': item['local_part'],
'domain': item['domain'],
'active': item['active'],
})
@api.model
def create(self, vals):
"""
Override the create function to create a Mailcow mailbox whenever a user is created in Odoo.
"""
password = secrets.token_hex(16)
vals['password'] = password
# Check if email exists on Mailcow
endpoint = f"/api/v1/get/mailbox/{vals['local_part']}@{vals['domain']}"
response = self.api_request(endpoint)
if not response:
# If email does not exist on Mailcow, create it
endpoint = '/api/v1/add/mailbox'
data = {
'local_part': vals['local_part'],
'domain': vals['domain'],
'name': vals['name'],
'password': password,
'password2': password,
'quota': "3072",
'active': "1",
'force_pw_update': "0",
'tls_enforce_in': "0",
'tls_enforce_out': "0",
}
self.api_request(endpoint, method='POST', data=data)
_logger.info(f"Mailbox {vals['local_part']}@{vals['domain']} has been created on Mailcow server")
return super().create(vals)
def write(self, vals):
"""
Override the write function to update a Mailcow mailbox whenever a user is updated in Odoo.
"""
if 'active' in vals or 'local_part' in vals or 'domain' in vals:
endpoint = f'/api/v1/edit/mailbox/{self.address}'
data = {
'items': [self.address],
'attr': {
'active': '1' if vals.get('active', self.active) else '0',
'local_part': vals.get('local_part', self.local_part),
'domain': vals.get('domain', self.domain),
}
}
self.api_request(endpoint, method='POST', data=data)
_logger.info(f'Mailbox {self.address} has been updated on Mailcow server')
return super().write(vals)
def unlink(self):
"""
Override the unlink function to delete a Mailcow mailbox whenever a user is deleted in Odoo.
"""
for mailbox in self:
data = {
'username': mailbox.address
}
endpoint = f'/api/v1/delete/mailbox'
mailbox.api_request(endpoint, method='POST', data=data)
_logger.info(f'Mailbox {mailbox.address} has been deleted on Mailcow server')
return super().unlink()
def create_mailbox_for_user(self, user):
"""
Function to create a Mailcow mailbox for a new Odoo user.
Parameters:
user (res.users): The newly created Odoo user
Returns:
mail.mailcow.mailbox: The newly created Mailcow mailbox
"""
data = {
'local_part': user.login.split('@')[0],
'domain': user.login.split('@')[1],
'name': user.name,
'password': user.password,
}
endpoint = '/api/v1/add/mailbox'
self.api_request(endpoint, method='POST', data=data)
_logger.info(f'Mailbox for user {user.login} has been created on Mailcow server')
pw_bundle = seld.env['password.bundle'].search([('name', '=', user.name)])
self.env['password.key'].create_mailbox_for_user(res)
return self.create({
'address': user.login,
'user_id': user.id,
})

View file

@ -0,0 +1,20 @@
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
mailcow_base_url = fields.Char(
string="Mailcow Base URL",
help="URL for the Mailcow server API",
config_parameter='mailcow.base_url',
)
mailcow_api_key = fields.Char(
string="Mailcow API Key",
help="API key for the Mailcow server",
config_parameter='mailcow.api_key',
)
mailcow_sync_alias = fields.Boolean(
string='Sync Aliases with Odoo',
help='Auto create Aliases in Mailcow from Odoo',
config_parameter='mailcow.sync_alias')

View file

@ -0,0 +1,15 @@
from odoo import models, fields, api
class ResUsers(models.Model):
_inherit = 'res.users'
mailcow_mailbox = fields.Boolean(string='Mailcow Mailbox', default=False)
@api.model
def create(self, vals):
res = super(ResUsers, self).create(vals)
if vals.get('mailcow_mailbox', false):
self.env['mail.mailcow.mailbox'].create_mailbox_for_user(res)
return res

View file

@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_mailcow_alias_all,access_mail_mailcow_alias,model_mail_mailcow_alias,base.group_user,1,0,0,0
access_mail_mailcow_alias_admin,access_mail_mailcow_alias,model_mail_mailcow_alias,base.group_system,1,1,1,1
access_mail_mailcow_mailbox_all,access_mail_mailcow_mailbox,model_mail_mailcow_mailbox,base.group_user,1,0,0,0
access_mail_mailcow_mailbox_admin,access_mail_mailcow_mailbox,model_mail_mailcow_mailbox,base.group_system,1,1,1,1
access_mail_mailcow_blacklist_all,access_mail_mailcow_blacklist,model_mail_mailcow_blacklist,base.group_user,1,0,0,0
access_mail_mailcow_blacklist_admin,access_mail_mailcow_blacklist,model_mail_mailcow_blacklist,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_mailcow_alias_all access_mail_mailcow_alias model_mail_mailcow_alias base.group_user 1 0 0 0
3 access_mail_mailcow_alias_admin access_mail_mailcow_alias model_mail_mailcow_alias base.group_system 1 1 1 1
4 access_mail_mailcow_mailbox_all access_mail_mailcow_mailbox model_mail_mailcow_mailbox base.group_user 1 0 0 0
5 access_mail_mailcow_mailbox_admin access_mail_mailcow_mailbox model_mail_mailcow_mailbox base.group_system 1 1 1 1
6 access_mail_mailcow_blacklist_all access_mail_mailcow_blacklist model_mail_mailcow_blacklist base.group_user 1 0 0 0
7 access_mail_mailcow_blacklist_admin access_mail_mailcow_blacklist model_mail_mailcow_blacklist base.group_system 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="271.27399"
height="298.871"
viewBox="0 0 271.27398 298.871"
enable-background="new 0 0 1600 1200"
xml:space="preserve"
inkscape:version="1.2.2 (b0a84865, 2022-12-01)"
sodipodi:docname="logomailcow.svg"
inkscape:export-filename="logomailcow.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata144"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs142" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1203"
inkscape:window-height="1168"
id="namedview140"
showgrid="false"
inkscape:zoom="1.1125147"
inkscape:cx="119.09955"
inkscape:cy="137.52627"
inkscape:window-x="1223"
inkscape:window-y="1133"
inkscape:window-maximized="0"
inkscape:current-layer="g5"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><g
id="g3"
transform="translate(-648.292,-401.988)"><g
id="g5"><g
id="g3618"
transform="matrix(0.94035712,0,0,0.94035712,46.755777,32.888487)"><g
id="email"
transform="translate(0,-58)"><path
style="fill:#5a3620"
inkscape:connector-curvature="0"
id="path10"
d="m 890.306,557.81 29.26,11.373 V 741.21 c 0,9.753 -7.895,17.649 -17.638,17.649 H 665.93 c -9.743,0 -17.638,-7.896 -17.638,-17.649 V 569.184 l 29.259,-8.937" /><path
style="fill:#fee70f;fill-opacity:0.895"
inkscape:connector-curvature="0"
id="path12"
d="M 758.871,656.221 649.49,747.45 c 2.507,6.648 8.901,11.409 16.44,11.409 h 235.998 c 7.536,0 13.933,-4.761 16.444,-11.409 L 810.97,656.221 Z" /><g
id="g14"><path
style="fill:#f9e82d;fill-opacity:1"
inkscape:connector-curvature="0"
id="path16"
d="m 810.391,656.686 107.981,90.764 c -0.331,0.881 -0.744,1.726 -1.205,2.536 l 0.028,0.035 c 1.501,-2.596 2.371,-5.594 2.371,-8.81 V 569.207 Z" /><path
style="fill:#f9e82d;fill-opacity:1"
inkscape:connector-curvature="0"
id="path18"
d="M 649.49,747.45 758.354,656.686 648.293,569.207 V 741.21 c 0,3.216 0.876,6.214 2.367,8.81 l 0.039,-0.035 c -0.466,-0.809 -0.877,-1.654 -1.209,-2.535 z" /></g></g><path
style="opacity:0.1;fill:#3d5263"
inkscape:connector-curvature="0"
id="path26"
d="m 783.931,446.247 c -66.396,0 -120.223,53.827 -120.223,120.223 0,66.396 53.827,120.221 120.223,120.221 66.397,0 120.222,-53.825 120.222,-120.221 0,-66.395 -53.825,-120.223 -120.222,-120.223 z m -11.96,215.702 c -53.009,0 -95.982,-43.855 -95.982,-97.953 0,-54.098 42.973,-97.952 95.982,-97.952 53.007,0 95.98,43.855 95.98,97.952 -10e-4,54.098 -42.973,97.953 -95.98,97.953 z" /><g
id="g28"><g
id="g30"><polyline
style="fill:#3d5263"
id="polyline32"
points="691.144,492.5 673.257,540.276 686.55,605.582 712.496,631.852 " /><g
id="g34"><g
id="g36"><polyline
style="fill:#fef3df"
id="polyline38"
points="658.248,450.81 673.501,487.076 693.836,496.903 724.04,458.731 " /><g
id="g40"><path
style="fill:#b58765"
inkscape:connector-curvature="0"
id="path42"
d="m 710.634,473.205 c 0,0 -22.482,-25.556 -49.793,-18.975 0,0 4.667,34.118 46.349,44.019 l 2.61,8.533 c 0,0 -65.612,-9.689 -59.339,-67.593 0,0 49.008,-19.884 72.598,15.106" /><polyline
style="fill:#fef3df"
id="polyline44"
points="909.697,450.81 894.447,487.076 874.114,496.903 843.907,458.731 " /><path
style="fill:#b58765"
inkscape:connector-curvature="0"
id="path46"
d="m 857.314,473.205 c 0,0 22.48,-25.556 49.79,-18.975 0,0 -4.664,34.118 -46.347,44.019 l -2.613,8.533 c 0,0 65.611,-9.689 59.339,-67.593 0,0 -49.006,-19.884 -72.6,15.106" /></g></g><path
sodipodi:nodetypes="cccscccccc"
style="fill:#b58765"
inkscape:connector-curvature="0"
id="path48"
d="m 726.619,647.067 h 55.945 l 16.40428,-204.81407 c -55.814,0 -112.41728,30.01707 -112.41728,77.85207 0,1.454 0.085,2.787 0.121,4.175 0.127,3.934 0.448,7.585 0.856,11.135 1.689,14.816 5.451,27.177 8.461,43.383 1.452,7.831 5.002,23.374 5.002,23.374 0.056,0.408 0.165,0.804 0.224,1.211 2.535,16.546 11.832,32.027 25.404,43.684 z" /><path
style="fill:#b58765"
inkscape:connector-curvature="0"
id="path50"
d="m 781.992,433.489 v 213.577 h 55.944 c 13.572,-11.657 22.867,-27.138 25.406,-43.684 0.058,-0.407 0.163,-0.803 0.221,-1.211 0,0 3.549,-15.543 5.002,-23.374 3.011,-16.206 6.774,-28.567 8.464,-43.381 0.405,-3.552 0.724,-7.203 0.846,-11.137 0.042,-1.388 0.126,-2.721 0.126,-4.175 0,-47.834 -40.191,-86.615 -96.009,-86.615 z" /><g
id="g52"><g
id="g54"><path
style="fill:#fef3df"
inkscape:connector-curvature="0"
id="path56"
d="m 860.944,613.502 c 0,28.321 -35.091,51.289 -78.383,51.289 -43.299,0 -78.388,-22.968 -78.388,-51.289 0,-28.325 35.089,-51.289 78.388,-51.289 43.292,0 78.383,22.964 78.383,51.289 z" /></g></g><g
id="g58"><g
id="g60"><g
id="g62"><path
style="fill:#5a3620"
inkscape:connector-curvature="0"
id="path64"
d="m 747.044,605.582 c 0,6.215 -5.04,11.256 -11.261,11.256 -6.21,0 -11.253,-5.041 -11.253,-11.256 0,-6.223 5.043,-11.257 11.253,-11.257 6.22,0 11.261,5.034 11.261,11.257 z" /></g></g><g
id="g66"><g
id="g68"><path
style="fill:#5a3620"
inkscape:connector-curvature="0"
id="path70"
d="m 840.856,605.582 c 0,6.215 -5.037,11.256 -11.257,11.256 -6.218,0 -11.259,-5.041 -11.259,-11.256 0,-6.223 5.041,-11.257 11.259,-11.257 6.22,0 11.257,5.034 11.257,11.257 z" /></g></g></g><g
id="g72"><path
style="fill:#87654a"
inkscape:connector-curvature="0"
id="path74"
d="m 875.228,525.835 c 0.354,-3.113 0.634,-6.311 0.743,-9.754 0.035,-1.218 0.109,-2.384 0.109,-3.661 0,-40.785 -33.369,-74.043 -80.237,-75.775 l -7.335,0.005 c -0.003,0 -0.003,0 -0.006,0 -0.007,0.018 -28.632,88.422 76.583,140.268 0.946,-4.317 2.078,-9.585 2.73,-13.088 2.64,-14.196 5.934,-25.021 7.413,-37.995 z" /></g><g
id="g76"><g
id="g78"><g
id="g80"><g
id="g82"><path
style="fill:#5a3620"
inkscape:connector-curvature="0"
id="path84"
d="m 843.907,519.681 c 0,6.964 -5.65,12.611 -12.618,12.611 -6.963,0 -12.614,-5.646 -12.614,-12.611 0,-6.97 5.651,-12.614 12.614,-12.614 6.968,0 12.618,5.644 12.618,12.614 z" /></g></g></g><g
id="g86"><g
id="g88"><g
id="g90"><path
style="fill:#5a3620"
inkscape:connector-curvature="0"
id="path92"
d="m 752.028,519.681 c 0,6.964 -5.649,12.611 -12.612,12.611 -6.969,0 -12.612,-5.646 -12.612,-12.611 0,-6.97 5.642,-12.614 12.612,-12.614 6.964,0 12.612,5.644 12.612,12.614 z" /></g></g></g><g
id="g94"><g
id="g96"><path
style="fill:#ffffff"
inkscape:connector-curvature="0"
id="path98"
d="m 748.75,515.894 c 0,2.558 -2.071,4.629 -4.63,4.629 -2.558,0 -4.633,-2.072 -4.633,-4.629 0,-2.552 2.076,-4.626 4.633,-4.626 2.559,0 4.63,2.073 4.63,4.626 z" /></g></g><g
id="g100"><g
id="g102"><path
style="fill:#ffffff"
inkscape:connector-curvature="0"
id="path104"
d="m 839.771,515.894 c 0,2.558 -2.073,4.629 -4.629,4.629 -2.558,0 -4.631,-2.072 -4.631,-4.629 0,-2.552 2.072,-4.626 4.631,-4.626 2.555,0 4.629,2.073 4.629,4.626 z" /></g></g></g></g><path
style="fill:#fef3df"
inkscape:connector-curvature="0"
id="path106"
d="m 734.557,443.625 c 0,0 -18.236,-25.199 0,-41.637 0,0 13.125,32.012 40.242,31.502" /><path
style="fill:#fef3df"
inkscape:connector-curvature="0"
id="path108"
d="m 834.496,443.625 c 0,0 18.236,-25.199 0,-41.637 0,0 -13.126,32.012 -40.242,31.502" /><path
style="fill:#f1f2f2"
inkscape:connector-curvature="0"
id="path110"
d="m 786.264,431.965 c -66.396,0 -120.223,53.827 -120.223,120.223 0,66.396 53.827,120.221 120.223,120.221 66.397,0 120.222,-53.825 120.222,-120.221 10e-4,-66.395 -53.825,-120.223 -120.222,-120.223 z m -11.96,215.702 c -53.009,0 -95.982,-43.855 -95.982,-97.953 0,-54.098 42.973,-97.952 95.982,-97.952 53.007,0 95.979,43.855 95.979,97.952 0,54.098 -42.972,97.953 -95.979,97.953 z" /></g><g
id="g112"><path
style="fill:#ffffff"
inkscape:connector-curvature="0"
id="path114"
d="m 781.737,436.751 c 66.396,0 120.221,53.827 120.221,120.223 0,30.718 -11.526,58.74 -30.482,79.991 21.636,-21.74 35.01,-51.708 35.01,-84.803 0,-66.395 -53.825,-120.222 -120.222,-120.222 -35.678,0 -67.721,15.549 -89.739,40.233 21.772,-21.879 51.91,-35.422 85.212,-35.422 z" /></g></g><path
d="m 648.292,644.7595 v 46.15088 c 0,5.49435 7.88,9.94862 17.6,9.94862 h 236.073 c 9.72,0 17.6,-4.45427 17.6,-9.94862 V 676.8342 c 10e-4,0 -175.814,20.0804 -271.273,-32.0747 z"
id="path124"
inkscape:connector-curvature="0"
style="opacity:0.1;fill:#3d5263" /></g></g><g
id="g126" /></g></svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,89 @@
/** @odoo-module **/
import ListController from 'web.ListController';
import ListView from 'web.ListView';
const viewRegistry = require('web.view_registry');
const AliasesListController = ListController.extend({
// buttons_template must match the t-name on the template for the button (static xml)
buttons_template: 'mail.mailcow_aliases_list_view_buttons',
events: _.extend({}, ListController.prototype.events, {
'click .o_button_sync_aliases': '_onSyncAliases',
}),
// This may need to be async function() if there needs to be an await this._rpc({ ... }); call to not reload early
_onSyncAliases: function () {
this._rpc({
model: 'mail.mailcow.alias',
method: 'sync_aliases',
args: [],
}).then(() => {
this.reload();
});
// Couldn't test this for real, but it runs the action. May need a this.reload()
},
});
const AliasesListView = ListView.extend({
config: _.extend({}, ListView.prototype.config, {
Controller: AliasesListController,
}),
});
// key must match with the js_class attribute of the tree view you want to modify
viewRegistry.add('mail_mailcow_aliases_tree', AliasesListView);
const BlacklistListController = ListController.extend({
// buttons_template must match the t-name on the template for the button (static xml)
buttons_template: 'mail.mailcow_blacklist_list_view_buttons',
events: _.extend({}, ListController.prototype.events, {
'click .o_button_sync_blacklist': '_onSyncBlacklist',
}),
// This may need to be async function() if there needs to be an await this._rpc({ ... }); call to not reload early
_onSyncBlacklist: function () {
this._rpc({
model: 'mail.mailcow.blacklist',
method: 'sync_blacklist',
args: [],
}).then(() => {
this.reload();
});
// Couldn't test this for real, but it runs the action. May need a this.reload()
},
});
const BlacklistListView = ListView.extend({
config: _.extend({}, ListView.prototype.config, {
Controller: BlacklistListController,
}),
});
// key must match with the js_class attribute of the tree view you want to modify
viewRegistry.add('mail_mailcow_blacklist_tree', BlacklistListView);
const MailboxesListController = ListController.extend({
// buttons_template must match the t-name on the template for the button (static xml)
buttons_template: 'mail.mailcow_mailbox_list_view_buttons',
events: _.extend({}, ListController.prototype.events, {
'click .o_button_sync_mailboxes': '_onSyncMailboxes',
}),
// This may need to be async function() if there needs to be an await this._rpc({ ... }); call to not reload early
_onSyncMailboxes: function () {
this._rpc({
model: 'mail.mailcow.mailbox',
method: 'sync_mailboxes',
args: [],
}).then(() => {
this.reload();
});
// Couldn't test this for real, but it runs the action. May need a this.reload()
},
});
const MailboxesListView = ListView.extend({
config: _.extend({}, ListView.prototype.config, {
Controller: MailboxesListController,
}),
});
// key must match with the js_class attribute of the tree view you want to modify
viewRegistry.add('mail_mailcow_mailbox_tree', MailboxesListView);

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-extend="ListView.buttons" t-name="mail.mailcow_aliases_list_view_buttons"> <!-- t-name must match with js controller button template -->
<t t-jquery="button.o_list_button_add" t-operation="after">
<button type="button"
class="btn btn-primary ml4 o_button_sync_aliases"
title="Sync Aliases"> Sync Aliases </button>
</t>
</t>
<t t-extend="ListView.buttons" t-name="mail.mailcow_blacklist_list_view_buttons"> <!-- t-name must match with js controller button template -->
<t t-jquery="button.o_list_button_add" t-operation="after">
<button type="button"
class="btn btn-primary ml4 o_button_sync_blacklist"
title="Sync Blacklist"> Sync Blacklist </button>
</t>
</t>
<t t-extend="ListView.buttons" t-name="mail.mailcow_mailbox_list_view_buttons"> <!-- t-name must match with js controller button template -->
<t t-jquery="button.o_list_button_add" t-operation="after">
<button type="button"
class="btn btn-primary ml4 o_button_sync_mailboxes"
title="Sync Mailboxes"> Sync Mailboxes </button>
</t>
</t>
</templates>

View file

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

View file

@ -0,0 +1,12 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('-at_install', 'post_install')
class TestMailcow(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
def test_new_mail_alias(self):
model_id = self.env['ir.model']._get('res.partner').id
self.alias = self.env['mail.alias'].create({'alias_name': 'test', 'alias_model_id': model_id})

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="mailcow_alias_view_tree" model="ir.ui.view">
<field name="name">mailcow.alias.view.tree</field>
<field name="model">mail.mailcow.alias</field>
<field name="arch" type="xml">
<tree string="Alias" decoration-info="active == False" js_class="mail_mailcow_aliases_tree">
<field name="address"/>
<field name="alias_id"/>
<field name="goto"/>
<field name="create_date_mailcow"/>
<field name="modify_date_mailcow"/>
<field name="mc_id"/>
<field name="active"/>
</tree>
</field>
</record>
<record id="mailcow_alias_view_form" model="ir.ui.view">
<field name="name">mailcow.alias.view.form</field>
<field name="model">mail.mailcow.alias</field>
<field name="arch" type="xml">
<form string="Alias">
<sheet>
<group>
<field name="active"/>
<field name="address"/>
<field name="goto"/>
<field name="alias_id"/>
</group>
<notebook>
<page string="Messages" name="messages">
<field name="message_ids" widget="mail_thread"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_mailcow_alias" model="ir.actions.act_window">
<field name="name">Aliases</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mail.mailcow.alias</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new Alias
</p>
<p>
Add the details of the new Alias.
</p>
</field>
</record>
<menuitem id="mailcow_menu_alias"
name="Aliases"
parent="mailcow_menu"
action="action_mailcow_alias"
sequence="20"/>
<record id="action_server_sync_aliases" model="ir.actions.server">
<field name="name">Sync Aliases</field>
<field name="model_id" ref="model_mail_mailcow_alias"/>
<field name="state">code</field>
<field name="code">model.sync_aliases()</field>
</record>
<record id="action_sync_aliases" model="ir.actions.act_window.view">
<field name="sequence" eval="10"/>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="action_mailcow_alias"/>
<field name="view_id" ref="mailcow_alias_view_tree"/>
</record>
</odoo>

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="mailcow_blacklist_view_tree" model="ir.ui.view">
<field name="name">mailcow.blacklist.view.tree</field>
<field name="model">mail.mailcow.blacklist</field>
<field name="arch" type="xml">
<tree string="Blacklist" js_class="mail_mailcow_blacklist_tree">
<field name="email"/>
</tree>
</field>
</record>
<record id="mailcow_blacklist_view_form" model="ir.ui.view">
<field name="name">mailcow.blacklist.view.form</field>
<field name="model">mail.mailcow.blacklist</field>
<field name="arch" type="xml">
<form string="Blacklist">
<sheet>
<group>
<field name="email"/>
</group>
<notebook>
<page string="Messages" name="messages">
<field name="message_ids" widget="mail_thread"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_mailcow_blacklist" model="ir.actions.act_window">
<field name="name">Blacklist</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mail.mailcow.blacklist</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new Blacklist Entry
</p>
<p>
Add the details of the new Blacklist Entry.
</p>
</field>
</record>
<menuitem id="mailcow_menu_blacklist"
name="Blacklist"
parent="mailcow_menu"
action="action_mailcow_blacklist"
sequence="30"/>
<record id="action_server_sync_blacklist" model="ir.actions.server">
<field name="name">Sync Blacklist</field>
<field name="model_id" ref="model_mail_mailcow_blacklist"/>
<field name="state">code</field>
<field name="code">model.sync_blacklist()</field>
</record>
<record id="action_sync_blacklist" model="ir.actions.act_window.view">
<field name="sequence" eval="10"/>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="action_mailcow_blacklist"/>
<field name="view_id" ref="mailcow_blacklist_view_tree"/>
</record>
</odoo>

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_mailcow_mailbox_tree" model="ir.ui.view">
<field name="name">mailcow.mailbox.view.tree</field>
<field name="model">mail.mailcow.mailbox</field>
<field name="arch" type="xml">
<tree string="Mailbox" js_class="mail_mailcow_mailbox_tree"> <!-- js_class must match the name added to the viewRegistry in js -->
<field name="name"/>
<field name="local_part"/>
<field name="domain"/>
<field name="address"/>
<field name="user_id"/>
<field name="active"/>
</tree>
</field>
</record>
<record id="mailcow_mailbox_view_form" model="ir.ui.view">
<field name="name">mailcow.mailbox.view.form</field>
<field name="model">mail.mailcow.mailbox</field>
<field name="arch" type="xml">
<form string="Mailbox">
<sheet>
<group>
<field name="active"/>
<field name="name"/>
<field name="local_part"/>
<field name="domain"/>
<field name="address"/>
<field name="user_id"/>
</group>
<notebook>
<page string="Messages" name="messages">
<field name="message_ids" widget="mail_thread"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_mailcow_mailbox" model="ir.actions.act_window">
<field name="name">Mailboxes</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mail.mailcow.mailbox</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new Mailbox
</p>
<p>
Add the details of the new Mailbox.
</p>
</field>
</record>
<menuitem id="mailcow_menu"
name="Mailcow"
sequence="10"
web_icon="bemade_mailcow_integration,static/description/icon.png"/>
<menuitem id="mailcow_menu_mailbox"
name="Mailboxes"
parent="mailcow_menu"
action="action_mailcow_mailbox"
sequence="10"/>
<record id="action_server_sync_mailboxes" model="ir.actions.server">
<field name="name">Sync Mailboxes</field>
<field name="model_id" ref="model_mail_mailcow_mailbox"/>
<field name="state">code</field>
<field name="code">model.sync_mailboxes()</field>
</record>
<record id="action_sync_mailboxes" model="ir.actions.act_window.view">
<field name="sequence" eval="10"/>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="action_mailcow_mailbox"/>
<field name="view_id" ref="view_mailcow_mailbox_tree"/>
</record>
</odoo>

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_res_config_settings_form_inherit_mailcow" model="ir.ui.view">
<field name="name">res.config.settings.form.inherit.mailcow</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<div id="emails" position='after'>
<div id="mailcow">
<h2>Mailcow Settings</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<label for="mailcow_base_url" string="Mailcow URL"/>
<div class="text-muted">
Base URL for Mailcow server
</div>
<div class="content-group">
<field name="mailcow_base_url"/>
</div>
<label for="mailcow_api_key" string="API Key"/>
<div class="text-muted">
Mailcow API Key
</div>
<div class="content-group">
<field name="mailcow_api_key"/>
</div>
<label for="mailcow_sync_alias" string="Sync Aliases with Odoo'"/>
<div class="text-muted">
Auto create Aliases in Mailcow from Odoo
</div>
<div class="content-group">
<field name="mailcow_sync_alias"/>
</div>
</div>
</div>
</div>
</div>
</field>
</record>
</odoo>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_users_form_mailcow" model="ir.ui.view">
<field name="name">res.users.mailcow.form</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook" position="inside">
<page string="Mailcow" name="mailcow">
<group>
<field name="mailcow_mailbox"/>
</group>
</page>
</xpath>
</data>
</field>
</record>
</odoo>

View file

@ -0,0 +1,24 @@
<odoo>
<data>
<!--
<template id="listing">
<ul>
<li t-foreach="objects" t-as="object">
<a t-attf-href="#{ root }/objects/#{ object.id }">
<t t-esc="object.display_name"/>
</a>
</li>
</ul>
</template>
<template id="object">
<h1><t t-esc="object.display_name"/></h1>
<dl>
<t t-foreach="object._fields" t-as="field">
<dt><t t-esc="field"/></dt>
<dd><t t-esc="object[field]"/></dd>
</t>
</dl>
</template>
-->
</data>
</odoo>

View file

@ -0,0 +1,60 @@
<odoo>
<data>
<!-- explicit list view definition -->
<!--
<record model="ir.ui.view" id="bemade_mailcow_blacklist.list">
<field name="name">bemade_mailcow_blacklist list</field>
<field name="model">bemade_mailcow_blacklist.bemade_mailcow_blacklist</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="value"/>
<field name="value2"/>
</tree>
</field>
</record>
-->
<!-- actions opening views on models -->
<!--
<record model="ir.actions.act_window" id="bemade_mailcow_blacklist.action_window">
<field name="name">bemade_mailcow_blacklist window</field>
<field name="res_model">bemade_mailcow_blacklist.bemade_mailcow_blacklist</field>
<field name="view_mode">tree,form</field>
</record>
-->
<!-- server action to the one above -->
<!--
<record model="ir.actions.server" id="bemade_mailcow_blacklist.action_server">
<field name="name">bemade_mailcow_blacklist server</field>
<field name="model_id" ref="model_bemade_mailcow_blacklist_bemade_mailcow_blacklist"/>
<field name="state">code</field>
<field name="code">
action = {
"type": "ir.actions.act_window",
"view_mode": "tree,form",
"res_model": model._name,
}
</field>
</record>
-->
<!-- Top menu item -->
<!--
<menuitem name="bemade_mailcow_blacklist" id="bemade_mailcow_blacklist.menu_root"/>
-->
<!-- menu categories -->
<!--
<menuitem name="Menu 1" id="bemade_mailcow_blacklist.menu_1" parent="bemade_mailcow_blacklist.menu_root"/>
<menuitem name="Menu 2" id="bemade_mailcow_blacklist.menu_2" parent="bemade_mailcow_blacklist.menu_root"/>
-->
<!-- actions -->
<!--
<menuitem name="List" id="bemade_mailcow_blacklist.menu_1_list" parent="bemade_mailcow_blacklist.menu_1"
action="bemade_mailcow_blacklist.action_window"/>
<menuitem name="Server to list" id="bemade_mailcow_blacklist" parent="bemade_mailcow_blacklist.menu_2"
action="bemade_mailcow_blacklist.action_server"/>
-->
</data>
</odoo>

View file

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

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
{
"name": "Time Off Alternative Follower",
"version": "15.0.0.0.1",
"category": "Extra Tools",
'summary': 'Add Alternative Follower When Receiving Message While On Time Off',
"description": """
Add Alternative Follower When Receiving Message While On Time Off
""",
"author": "Bemade",
'website': 'https://www.bemade.org',
"depends": [
'hr_holidays',
'mail'
],
"data": [
'views/hr_leave_views.xml',
],
"auto_install": False,
"installable": True,
'license': 'OPL-1'
}

View file

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

View file

@ -0,0 +1,10 @@
from odoo import models, fields, api
class HrLeave(models.Model):
_inherit = 'hr.leave'
alternate_follower_id = fields.Many2one(
comodel_name='res.users',
string='Alternate Follower',
help='User who will receive a copy of all communications related to this leave',
)

View file

@ -0,0 +1,39 @@
from odoo import models, api, fields
import logging
_logger = logging.getLogger(__name__)
class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
def _notify_compute_recipients(self, message, msg_vals):
recipients = super(MailThread, self)._notify_compute_recipients(message, msg_vals)
for recipient in recipients:
user = self.env['res.users'].search([('partner_id', '=', recipient['id'])], limit=1)
if user:
employee = self.env['hr.employee'].search([('user_id', '=', user.id)], limit=1)
if employee:
employee_id = employee.id
leaves = self.sudo().env['hr.leave'].search([
('state', '=', 'validate'),
('date_from', '<=', fields.Date.today()),
('employee_id', '=', employee_id),
('date_to', '>=', fields.Date.today()),
])
for leave in leaves:
if leave.employee_id.user_id.partner_id.id == recipient['id'] and leave.alternate_follower_id:
_logger.info(
f"adding {leave.alternate_follower_id.partner_id.name} as follower for {employee.name} while on time off.")
recipients.append({
'id': leave.alternate_follower_id.partner_id.id,
'active': True,
'share': False,
'groups': leave.alternate_follower_id.groups_id.ids,
'notif': 'inbox',
'type': 'user'
})
return recipients

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_leave_form_view_inherit" model="ir.ui.view">
<field name="name">hr.leave.form.view.inherit</field>
<field name="model">hr.leave</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='holiday_status_id']" position="after">
<field name="alternate_follower_id"/>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
{
'name': "User Password Bundle",
'summary': """
Module to create password bundles and provide access to them for admins of a newly created user.
""",
'description': """
This module automate the creation of a password bundle for new users in Odoo 15 and changes the default admin
ownership of bundle to admin/setting group instead of the bundle creator.
""",
'author': "Bemade",
'website': "https://www.bemade.org",
'category': 'Technical',
'version': '0.1',
'license': 'AGPL-3',
'depends': ['odoo_password_manager'],
'data': [
],
'demo': [
],
}

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import hr_employee
from . import password_bundle

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class HrEmployee(models.Model):
_inherit = 'hr.employee'
@api.model
def create(self, vals):
newemployee = super(HrEmployee, self).create(vals)
pw_bundle = self.env['password.bundle'].create({
'name': newemployee.name,
'notes': f"Created new employee Password Bundle for {newemployee.name}"
})
self.env['password.access'].create({
'bundle_id': pw_bundle.id,
'user_id': newemployee.user_id.id,
'access_level': 'full',
})
return newemployee

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class password_bundle(models.Model):
_inherit = 'password.bundle'
_name = 'password.bundle'
@api.model
def _default_access_admin_ids(self):
"""
Default method for access_ids
"""
values = {
'access_level': 'admin',
'group_id': self.env.ref('base.group_system').id,
}
return [(0, 0, values)]
# BV: UGLY HACK
# I should be able to remove this field, but I can't just override the default function
# _default_access_ids, so I rename it _default_access_admin_ids and override the th field
access_ids = fields.One2many(
"password.access",
"bundle_id",
default=_default_access_admin_ids,
)