Files

220 lines
7.9 KiB
Python
Raw Permalink Normal View History

2026-07-01 14:41:49 +07:00
import psycopg2
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import secrets
import string
import subprocess
import logging
from datetime import datetime, timedelta
_logger = logging.getLogger(__name__)
class DatabaseInstance(models.Model):
_name = 'database.instance'
_description = 'Database Instance'
_rec_name = 'database_name'
subscription_id = fields.Many2one('user.subscription', string='Subscription',
required=True, ondelete='cascade')
database_name = fields.Char(string='Database Name', required=True, index=True)
subdomain = fields.Char(string='Subdomain', required=True)
full_domain = fields.Char(string='Full Domain', compute='_compute_full_domain')
# Database status
db_state = fields.Selection([
('pending', 'Pending'),
('creating', 'Creating'),
('installing', 'Installing Apps'),
('ready', 'Ready'),
('failed', 'Failed'),
], string='Status', default='pending', tracking=True)
# PostgreSQL connection
pg_host = fields.Char(string='PostgreSQL Host', default='localhost')
pg_port = fields.Integer(string='PostgreSQL Port', default=5432)
pg_user = fields.Char(string='Database User')
pg_password = fields.Char(string='Database Password')
# Instance configuration
workers = fields.Integer(string='Workers', default=2)
timeout = fields.Integer(string='Timeout (seconds)', default=300)
memory_limit_mb = fields.Integer(string='Memory Limit (MB)', default=512)
max_connections = fields.Integer(string='Max Connections', default=20)
# Timestamps
created_at = fields.Datetime(string='Created At', default=fields.Datetime.now)
ready_at = fields.Datetime(string='Ready At')
last_accessed = fields.Datetime(string='Last Accessed')
# Admin credentials
admin_login = fields.Char(string='Admin Login')
admin_password = fields.Char(string='Admin Password')
admin_email = fields.Char(string='Admin Email')
installed_apps = fields.Text(string='Installed Apps')
error_message = fields.Text(string='Error Message')
@api.depends('subdomain')
def _compute_full_domain(self):
base_domain = self.env['ir.config_parameter'].sudo().get_param('web.base.domain', 'localhost')
for record in self:
if record.subdomain:
record.full_domain = f"{record.subdomain}.{base_domain}"
else:
record.full_domain = False
def generate_unique_subdomain(self, company_name):
"""Generate unique subdomain from company name"""
import re
# Convert to lowercase and remove special characters
base = re.sub(r'[^a-z0-9]+', '-', company_name.lower()).strip('-')
# Add random string
random_str = ''.join(secrets.choice(string.digits) for _ in range(5))
subdomain = f"{base}-{random_str}"
# Ensure uniqueness
counter = 1
original_subdomain = subdomain
while self.search_count([('subdomain', '=', subdomain)]) > 0:
subdomain = f"{original_subdomain}-{counter}"
counter += 1
return subdomain
def generate_password(self, length=12):
"""Generate secure random password"""
chars = string.ascii_letters + string.digits + "!@#$%^&*"
return ''.join(secrets.choice(chars) for _ in range(length))
def create_database(self):
"""Create PostgreSQL database for tenant"""
self.ensure_one()
try:
self.db_state = 'creating'
self._cr.commit()
# Generate credentials
self.pg_user = f"user_{self.database_name.replace('-', '_')}"
self.pg_password = self.generate_password()
self.admin_login = 'admin'
self.admin_password = self.generate_password()
# Connect to PostgreSQL
conn = psycopg2.connect(
host=self.pg_host,
port=self.pg_port,
database='postgres',
user='odoo', # Master PostgreSQL user
password='odoo_password' # Should be in config
)
conn.autocommit = True
cursor = conn.cursor()
# Create database user
try:
cursor.execute(f"DROP ROLE IF EXISTS {self.pg_user}")
cursor.execute(
f"CREATE ROLE {self.pg_user} WITH LOGIN PASSWORD %s",
(self.pg_password,)
)
except Exception as e:
_logger.error(f"Error creating user: {e}")
# Create database
cursor.execute(f"DROP DATABASE IF EXISTS {self.database_name}")
cursor.execute(
f"CREATE DATABASE {self.database_name} "
f"OWNER {self.pg_user} "
f"ENCODING 'UTF8' "
f"TEMPLATE template0"
)
# Set permissions
cursor.execute(
f"GRANT ALL PRIVILEGES ON DATABASE {self.database_name} TO {self.pg_user}"
)
cursor.close()
conn.close()
_logger.info(f"Database {self.database_name} created successfully")
return True
except Exception as e:
self.db_state = 'failed'
self.error_message = str(e)
_logger.error(f"Failed to create database: {e}")
return False
def install_apps(self, app_list):
"""Install selected Odoo apps in the database"""
self.ensure_one()
try:
self.db_state = 'installing'
self.installed_apps = ', '.join(app_list)
self._cr.commit()
# This would typically use Odoo's database provisioning API
# For now, we'll simulate it
import odoo
from odoo import api, SUPERUSER_ID
# Initialize new database
registry = odoo.modules.registry.Registry.new(
self.database_name,
force_demo=False,
signal=False,
update_module=True
)
with api.Environment.manage(), registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
# Install selected modules
for module_name in app_list:
try:
module = env['ir.module.module'].search([('name', '=', module_name)])
if module:
module.button_immediate_install()
_logger.info(f"Installed module: {module_name}")
except Exception as e:
_logger.warning(f"Failed to install {module_name}: {e}")
# Create admin user
env['res.users'].create({
'name': 'Administrator',
'login': self.admin_login,
'password': self.admin_password,
'email': self.admin_email,
'groups_id': [(6, 0, [env.ref('base.group_system').id])],
})
self.db_state = 'ready'
self.ready_at = fields.Datetime.now()
_logger.info(f"Database {self.database_name} is ready")
return True
except Exception as e:
self.db_state = 'failed'
self.error_message = str(e)
_logger.error(f"Failed to install apps: {e}")
return False
def configure_instance(self):
"""Configure system instance parameters"""
self.ensure_one()
# Update odoo.conf or use environment variables
config_params = {
'workers': self.workers,
'timeout': self.timeout,
'limit_memory_hard': self.memory_limit_mb * 1024 * 1024,
'max_cron_threads': 1,
}
# These would be applied at the Odoo server level
_logger.info(f"Configuring instance {self.database_name}: {config_params}")
return True