apps_features

This commit is contained in:
esam 2025-10-01 01:15:19 -04:00
parent 004ec42038
commit 1d70e2c81b
15 changed files with 427 additions and 0 deletions

View File

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

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
{
'name': 'Odex30-Apps Feature',
'version': '18.0.0.1',
'summary': 'Enhance Odoo with Addons Path Management and Module History Tracking',
'summary': 'Enhance Odoo with Addons Path Management and History Tracking, with Uninstall Restrictions',
'description': """
This custom module extends Odoo's functionality by introducing features related to Addons Path management and tracking module installation, upgrades, and uninstallations.
Key Features:
- Addons Path Management: Organize and manage Addons Paths with customizable short names and visually distinctive colors.
- Module History Tracking: Keep a record of module installation, upgrades, and uninstallations, including the author and timestamp.
- Uninstall Restrictions: Admins can set an uninstallation password to add an extra layer of security, preventing unauthorized module removal.
Note: This module should be used by experienced Odoo administrators to enhance system management and tracking capabilities. The Uninstall Restrictions feature adds an extra layer of security to prevent unauthorized module removal.
""",
'category': 'Odex30-base',
'author': "Expert Co. Ltd.",
'website': "http://www.exp-sa.com",
'license': 'AGPL-3',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/ir_module_module_view.xml',
'views/ir_module_addons_path_view.xml'
,'views/upgrade_button_views.xml'
,'wizard/base_module_uninstall_views.xml'
,'views/ir_module_history_view.xml',
'views/module_multiple_uninstall.xml',
],
'demo': [
],
'qweb': [],
'installable': True,
'auto_install': False,
'application': False,
}

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import ir_module_module
from . import ir_module_addons_path
from . import ir_module_history

View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
import random
from odoo import models, fields
class IrModuleAddonsPath(models.Model):
_name = "ir.module.addons.path"
_description = "Addons Path"
def _default_bg_color(self):
colors = ['#F06050', '#F4A45F', '#F7CD2E', '#6CC1ED', '#EB7E7F', '#5CC482',
'#2c8297', '#D8485E', '#9365B8', '#804967', '#475576', ]
res = '#FFFFFF'
try:
res = random.choice(colors)
except:
pass
return res
name = fields.Char(string='Short Name')
path = fields.Char(string='Full Path')
path_temp = fields.Char(string='Shortened Path')
color = fields.Char(default=_default_bg_color)
module_ids = fields.One2many('ir.module.module', 'addons_path_id')
module_count = fields.Integer(compute='_compute_module_count')
def _compute_module_count(self):
for rec in self:
rec.module_count = len(rec.module_ids)
def open_apps_view(self):
self.ensure_one()
return {'type': 'ir.actions.act_window',
'name': 'Apps',
'view_mode': 'kanban,list,form',
'res_model': 'ir.module.module',
'context': {},
'domain': [('addons_path_id', '=', self.id)],
}

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class IrModuleHistory(models.Model):
_name = 'ir.module.history'
_description = 'Module History'
TYPES = [
('install', 'Installed'),
('upgrade', 'Upgraded'),
('uninstall', 'Uninstalled'),
]
module_name = fields.Char(required=True, string='Module')
type = fields.Selection(TYPES, required=True, string='Action')
user_id = fields.Many2one('res.users', string='Author', required=True)
class IrModuleModule(models.Model):
_inherit = 'ir.module.module'
def _button_immediate_function(self, function):
res = super(IrModuleModule, self)._button_immediate_function(function)
for module in self:
action_type = {
'button_install': 'install',
'button_upgrade': 'upgrade',
'button_uninstall': 'uninstall',
}.get(function.__name__)
if action_type:
self.env['ir.module.history'].sudo().create({
'module_name': module.name,
'type': action_type,
'user_id': self._uid,
})
return res

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.modules.module import get_module_path
class IrModule(models.Model):
_inherit = "ir.module.module"
addons_path_id = fields.Many2one('ir.module.addons.path', string='Addons Path Record', readonly=True)
addons_path = fields.Char(string='Addons Path', related='addons_path_id.path', readonly=True)
def module_multiple_uninstall(self):
modules_uninstall = self.browse(self.env.context.get('active_ids')).button_immediate_uninstall()
class BaseModuleUpdate(models.TransientModel):
_inherit = "base.module.update"
def update_addons_paths(self):
from odoo import addons
addons_path_obj = self.env['ir.module.addons.path']
ad_paths = addons.__path__
for path in ad_paths:
if not addons_path_obj.search([('path', '=', path)]):
path_temp = path
if len(path_temp) > 42:
path_temp = '%s......%s' % (path[:12], path[-19:])
addons_path_obj.sudo().create({
'name': path.split('/')[-1],
'path': path,
'path_temp': path_temp,
})
for addons_path_id in addons_path_obj.search([]):
if addons_path_id.path not in ad_paths:
addons_path_id.unlink()
def update_module_addons_paths(self):
addons_path_obj = self.env['ir.module.addons.path']
for module_id in self.env['ir.module.module'].search([]):
module_path = get_module_path(module_id.name)
if not module_path:
continue
addons_path = module_path.rstrip(module_id.name).rstrip('/')
addons_path_id = addons_path_obj.search([('path', '=', addons_path)])
if addons_path_id:
module_id.addons_path_id = addons_path_id.id
def update_module(self):
result = super(BaseModuleUpdate, self).update_module()
self.sudo().update_addons_paths()
self.sudo().update_module_addons_paths()
return result

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ir_module_addons_path,ir.module.addons.path access,model_ir_module_addons_path,base.group_system,1,1,1,1
access_ir_module_history,ir.module.history access,model_ir_module_history,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_ir_module_addons_path ir.module.addons.path access model_ir_module_addons_path base.group_system 1 1 1 1
3 access_ir_module_history ir.module.history access model_ir_module_history base.group_system 1 1 1 1

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!--Kanban-->
<record id="view_kanban_ir_module_addons_path" model="ir.ui.view">
<field name="name">ir.module.addons.path.kanban</field>
<field name="model">ir.module.addons.path</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile" create="0" edit="0" delete="0">
<field name="name"/>
<field name="path"/>
<field name="path_temp"/>
<field name="color"/>
<templates>
<t t-name="kanban-box">
<div t-attf-style="border-left: 3px solid {{record.color.value}}" t-attf-class="oe_kanban_global_click">
<div class="oe_kanban_details">
<strong class="o_kanban_record_title"><field name="name"/></strong>
<ul>
<li class="text-muted"><field name="path_temp"/></li>
<li>
<a name="open_apps_view" href="#" type="object"><field name="module_count"/> Modules</a>
</li>
</ul>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!--Tree-->
<record id="view_tree_ir_module_addons_path" model="ir.ui.view">
<field name="name">ir.module.addons.path.view.tree</field>
<field name="model">ir.module.addons.path</field>
<field name="arch" type="xml">
<list create="0" edit="0" delete="1">
<field name="name"/>
<field name="path"/>
<field name="path_temp" invisible="1"/>
<field name="color" invisible="1"/>
</list>
</field>
</record>
<!--Form-->
<record id="view_form_ir_module_addons_path" model="ir.ui.view">
<field name="name">ir.module.addons.path.view.form</field>
<field name="model">ir.module.addons.path</field>
<field name="arch" type="xml">
<form create="0" delete="0">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" placeholder="My-Addons"/></h1>
</div>
<group>
<field name="path" readonly="1"/>
</group>
</sheet>
</form>
</field>
</record>
<!--Action-->
<record id="action_view_ir_module_addons_path" model="ir.actions.act_window">
<field name="name">Addons Paths</field>
<field name="res_model">ir.module.addons.path</field>
<field name="view_mode">kanban,list,form</field>
<field name="context">{}</field>
<field name="domain">[]</field>
<field name="help" type="html">
<p>Please Update the Apps List ...</p>
</field>
</record>
<!--Menu-->
<menuitem name="Addons Paths"
id="menu_view_ir_module_addons_path"
parent="base.menu_management"
action="action_view_ir_module_addons_path"
sequence="6"
groups="base.group_system"/>
</odoo>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!--Form-->
<record id="view_tree_ir_module_history" model="ir.ui.view">
<field name="name">ir.module.history.view.tree</field>
<field name="model">ir.module.history</field>
<field name="arch" type="xml">
<list create="0" edit="0" default_order="create_date desc" decoration-danger="type=='uninstall'" delete="0">
<field name="create_date" string="Time"/>
<field name="module_name"/>
<field name="type"/>
<field name="user_id"/>
</list>
</field>
</record>
<!--Action-->
<record id="action_view_ir_module_history" model="ir.actions.act_window">
<field name="name">History</field>
<field name="res_model">ir.module.history</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
No history
</p>
</field>
</record>
<!--Menu-->
<menuitem name="History" id="menu_view_ir_module_history" parent="base.menu_management" action="action_view_ir_module_history" sequence="200" groups="base.group_system"/>
</odoo>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!--Search-->
<record id="view_search_ir_module_module_inherit" model="ir.ui.view">
<field name="name">ir.module.module.view.search.inherit.app.addons.path</field>
<field name="model">ir.module.module</field>
<field name="inherit_id" ref="base.view_module_filter"/>
<field name="arch" type="xml">
<xpath expr="/search/group" position="inside">
<filter string="Addons Path" name="addons_path" domain="[]" context="{'group_by':'addons_path_id'}"/>
</xpath>
</field>
</record>
<!--Form-->
<record id="view_form_ir_module_module_inherit" model="ir.ui.view">
<field name="name">ir.module.module.view.form.inherit.app.addons.path</field>
<field name="model">ir.module.module</field>
<field name="inherit_id" ref="base.module_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='name']/parent::group/parent::group" position="after">
<group groups="base.group_system">
<field name="addons_path_id" invisible="1"/>
<field name="addons_path"/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!--created an action server which appeared in action button with name Modules Un-Install-->
<record id="action_server_module_multiple_uninstall" model="ir.actions.server">
<field name="name">Modules Un-Install</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_ir_module_module"/>
<field name="binding_model_id" ref="model_ir_module_module"/>
<field name="state">code</field>
<field name="code">records.module_multiple_uninstall()</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="custom_upgrade_btn" model="ir.ui.view">
<field name="name">Apps Kanban.Inherit.UpgradeButton</field>
<field name="model">ir.module.module</field>
<field name="inherit_id" ref="base.module_view_kanban"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('text-muted')][contains(., 'Installed')]" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//templates/t[@t-name='card']/main/footer" position="inside">
<div t-if="record.state.raw_value == 'installed'" class="o_module_action_buttons">
<button type="object"
class="btn btn-warning btn-sm me-1"
name="button_immediate_upgrade"
string="Upgrade">
<i class="fa fa-upload me-1"/>Upgrade
</button>
<button type="object"
class="btn btn-danger btn-sm"
name="button_uninstall_wizard"
string="Uninstall">
<i class="fa fa-trash me-1"/>Uninstall
</button>
</div>
</xpath>
</field>
</record>
</odoo>

View File

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

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, tools
from odoo.exceptions import ValidationError
class BaseModuleUninstall(models.TransientModel):
_inherit = "base.module.uninstall"
is_restrict = fields.Boolean()
password = fields.Char(required=True)
def action_uninstall(self):
if not tools.config.get('uninstall_password'):
raise ValidationError("Uninstall password not yet set!")
if self.password != tools.config.get('uninstall_password').replace('\'', ''):
raise ValidationError("Invalid Password!")
return super(BaseModuleUninstall, self).action_uninstall()

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_base_module_uninstall_with_password" model="ir.ui.view">
<field name="name">Uninstall module</field>
<field name="model">base.module.uninstall</field>
<field name="inherit_id" ref="base.view_base_module_uninstall"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='show_all']" position="before">
<p class='alert alert-danger' role="status">
<group>
<field name="password" string="Uninstall Password" password="True"/>
<hr style="display:none;"/>
</group>
</p>
</xpath>
</field>
</record>
</odoo>