77 lines
3.2 KiB
Python
77 lines
3.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
from odoo import api, fields, models
|
|
|
|
|
|
class SaasApp(models.Model):
|
|
"""Mirrors REAL installed Odoo "apps" (not technical dependency modules)
|
|
so the public /trial "Choose your Apps" page only shows what is
|
|
actually installed on this internal instance - exactly like the
|
|
odoo.com/trial reference page.
|
|
|
|
Odoo's ir.module.module already flags genuine apps via the
|
|
`application = True` field (set by each module's __manifest__.py
|
|
with 'application': True). Pure dependency/technical modules
|
|
(e.g. 'mail', 'web', 'base_setup') have application = False and are
|
|
therefore excluded automatically - no manual curation needed.
|
|
"""
|
|
_name = 'saas.app'
|
|
_description = 'SaaS Sellable App (synced from installed Odoo apps)'
|
|
_order = 'category_sequence, sequence, name'
|
|
|
|
name = fields.Char(required=True)
|
|
technical_module_name = fields.Char(
|
|
required=True,
|
|
help="Technical name of the Odoo module to install, e.g. 'website', 'crm', 'account'."
|
|
)
|
|
module_id = fields.Many2one('ir.module.module', string='Source Module', ondelete='cascade')
|
|
icon = fields.Image('Icon', max_width=128, max_height=128)
|
|
icon_url = fields.Char() # fallback path to module's static icon if no binary icon
|
|
description = fields.Text()
|
|
category_id = fields.Many2one('ir.module.category', string='App Category')
|
|
category_sequence = fields.Integer(related='category_id.sequence', store=True)
|
|
sequence = fields.Integer(default=10)
|
|
active = fields.Boolean(default=True)
|
|
|
|
_sql_constraints = [
|
|
('module_unique', 'unique(module_id)', 'Each installed app can only be listed once.'),
|
|
]
|
|
|
|
@api.model
|
|
def _sync_from_installed_modules(self):
|
|
"""Step: Internal Apps Mirror
|
|
Reads every module on THIS Odoo instance that is:
|
|
- state = 'installed' (actually installed internally)
|
|
- application = True (a real "app", not a dependency module)
|
|
and upserts a saas.app record for each, so the public /trial page
|
|
always reflects exactly what is installed - nothing more.
|
|
"""
|
|
Module = self.env['ir.module.module'].sudo()
|
|
installed_apps = Module.search([
|
|
('state', '=', 'installed'),
|
|
('application', '=', True),
|
|
])
|
|
|
|
existing = self.sudo().search([])
|
|
existing_by_module = {a.module_id.id: a for a in existing}
|
|
|
|
for module in installed_apps:
|
|
vals = {
|
|
'name': module.shortdesc or module.name,
|
|
'technical_module_name': module.name,
|
|
'module_id': module.id,
|
|
'description': module.summary or module.description,
|
|
'category_id': module.category_id.id if module.category_id else False,
|
|
'icon_url': f'/{module.name}/static/description/icon.png',
|
|
'active': True,
|
|
}
|
|
if module.id in existing_by_module:
|
|
existing_by_module[module.id].sudo().write(vals)
|
|
else:
|
|
self.sudo().create(vals)
|
|
|
|
# Deactivate saas.app entries whose module got uninstalled meanwhile
|
|
stale = existing.filtered(lambda a: a.module_id.id not in installed_apps.ids)
|
|
stale.sudo().write({'active': False})
|
|
|
|
return True
|