Alaguraj0361 b9d5617051 Add SaaS multi-tenant models and views for restaurant management
- Introduced `saas.plan` model to define subscription plans with limits and pricing.
- Created `saas.restaurant` model to manage restaurant tenants, including database provisioning and subscription management.
- Implemented views for managing SaaS plans and restaurant tenants, including tree and form views.
- Added security access rights for the new models.
- Developed a backup management view for database backups.
- Updated menu structure to include new SaaS management options.
- Added Docker and deployment configurations for PostgreSQL, Redis, and Odoo services.
- Included scaling guide and backup scripts for production environments.
- Enhanced theme with new images and layout adjustments.
2026-06-19 15:03:51 +05:30

118 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
import os
import tempfile
import logging
from datetime import datetime
from odoo import models, fields, api
from odoo.exceptions import UserError
from odoo.service import db
_logger = logging.getLogger(__name__)
try:
import boto3
except ImportError:
boto3 = None
class SaasBackup(models.Model):
_name = 'saas.backup'
_description = 'Dine360 SaaS Backup Log'
_order = 'timestamp desc'
restaurant_id = fields.Many2one('saas.restaurant', string='Restaurant Tenant', required=True)
database_name = fields.Char(related='restaurant_id.database_name', store=True, readonly=True)
backup_file = fields.Char(string='Backup Filename', readonly=True)
timestamp = fields.Datetime(string='Backup Time', default=fields.Datetime.now, readonly=True)
status = fields.Selection([
('success', 'Success'),
('failed', 'Failed')
], string='Status', default='failed', readonly=True)
storage_type = fields.Selection([
('local', 'Local Storage'),
('s3', 'Amazon S3')
], string='Storage Type', default='local', required=True)
s3_url = fields.Char(string='S3 URL', readonly=True)
def action_backup_database(self):
self.ensure_one()
db_name = self.database_name
if not db_name:
raise UserError("Database name is missing on the restaurant record!")
timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"{db_name}_{timestamp_str}.zip"
# Create temporary file to store Odoo dump
temp_dir = tempfile.gettempdir()
temp_filepath = os.path.join(temp_dir, filename)
try:
_logger.info(f"Dumping database {db_name} to {temp_filepath}...")
with open(temp_filepath, 'wb') as f:
# Odoo service dump database to stream
db.dump_db(db_name, f, format='zip')
_logger.info("Database dumped successfully.")
# S3 upload logic
s3_bucket = os.environ.get('AWS_S3_BUCKET')
aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID')
aws_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
if self.storage_type == 's3' and boto3 and s3_bucket:
_logger.info(f"Uploading {filename} to AWS S3 bucket {s3_bucket}...")
s3_client = boto3.client(
's3',
aws_access_key_id=aws_access_key,
aws_secret_access_key=aws_secret_key
)
s3_client.upload_file(temp_filepath, s3_bucket, filename)
s3_url = f"https://{s3_bucket}.s3.amazonaws.com/{filename}"
self.write({
'backup_file': filename,
'status': 'success',
's3_url': s3_url
})
# Clean up local temp file
os.remove(temp_filepath)
else:
# Local storage: move backup to persistent local directory
backup_dir = os.environ.get('ODOO_BACKUP_DIR', '/var/lib/odoo/backups')
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
persistent_path = os.path.join(backup_dir, filename)
os.rename(temp_filepath, persistent_path)
self.write({
'backup_file': filename,
'status': 'success',
's3_url': f"file://{persistent_path}"
})
except Exception as e:
_logger.error(f"Backup generation failed: {str(e)}")
self.write({
'backup_file': filename,
'status': 'failed'
})
if os.path.exists(temp_filepath):
os.remove(temp_filepath)
raise UserError(f"Backup failed: {str(e)}")
@api.model
def cron_automatic_backups(self):
""" Triggered by Odoo cron job to run daily backups for all active tenants """
tenants = self.env['saas.restaurant'].search([('status', '=', 'active')])
s3_bucket = os.environ.get('AWS_S3_BUCKET')
storage = 's3' if s3_bucket else 'local'
for tenant in tenants:
try:
backup = self.create({
'restaurant_id': tenant.id,
'storage_type': storage
})
backup.action_backup_database()
except Exception as e:
_logger.error(f"Scheduled backup failed for {tenant.database_name}: {str(e)}")