Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
from . import color_editor
from . import ir_http
from . import res_company
from . import res_config_settings
from . import res_users
from . import res_users_settings

View File

@@ -0,0 +1,133 @@
import re
import base64
from odoo import models, fields, api
from odoo.tools import misc
from odoo.addons.base.models.assetsbundle import EXTENSIONS
class ColorEditor(models.AbstractModel):
_name = 'fusion_backend_theme.color_editor'
_description = 'Theme Color Editor'
@api.model
def _get_custom_url(self, url, bundle):
return f'/_custom/{bundle}{url}'
@api.model
def _get_url_info(self, url):
regex = re.compile(
r'^(/_custom/([^/]+))?/(\w+)/([/\w.]+\.\w+)$'
)
match = regex.match(url)
if not match:
return False
return {
'module': match.group(3),
'resource_path': match.group(4),
'customized': bool(match.group(1)),
'bundle': match.group(2) or False
}
@api.model
def _find_attachment(self, custom_url):
return self.env['ir.attachment'].search([
('url', '=', custom_url)
])
@api.model
def _find_asset(self, custom_url):
return self.env['ir.asset'].search([
('path', 'like', custom_url)
])
@api.model
def _read_scss(self, url, bundle):
custom_url = self._get_custom_url(url, bundle)
url_info = self._get_url_info(custom_url)
if url_info and url_info['customized']:
attachment = self._find_attachment(custom_url)
if attachment:
return base64.b64decode(attachment.datas)
with misc.file_open(
url.strip('/'), 'rb', filter_ext=EXTENSIONS
) as f:
return f.read()
def _extract_variable(self, content, variable):
value = re.search(
fr'\$o_fusion_{variable}\:?\s(.*?);', content
)
return value and value.group(1)
def _extract_variables(self, content, variables):
return {
var: self._extract_variable(content, var)
for var in variables
}
def _update_variables(self, content, variables):
for variable in variables:
if not variable.get("value"):
continue
content = re.sub(
fr'{variable["name"]}\:?\s(.*?);',
f'{variable["name"]}: {variable["value"]};',
content
)
return content
@api.model
def _write_scss(self, url, bundle, content):
custom_url = self._get_custom_url(url, bundle)
asset_url = url[1:] if url.startswith(('/', '\\')) else url
datas = base64.b64encode((content or '\n').encode('utf-8'))
custom_attachment = self._find_attachment(custom_url)
if custom_attachment:
custom_attachment.write({'datas': datas})
self.env.registry.clear_cache('assets')
else:
attachment_values = {
'name': url.split('/')[-1],
'type': 'binary',
'mimetype': 'text/scss',
'datas': datas,
'url': custom_url,
}
asset_values = {
'path': custom_url,
'target': url,
'directive': 'replace',
}
target_asset = self._find_asset(asset_url)
if target_asset:
asset_values['name'] = '%s override' % target_asset.name
asset_values['bundle'] = target_asset.bundle
asset_values['sequence'] = target_asset.sequence
else:
asset_values['name'] = '%s: replace %s' % (
bundle, custom_url.split('/')[-1]
)
asset_values['bundle'] = (
self.env['ir.asset']._get_related_bundle(url, bundle)
)
self.env['ir.attachment'].create(attachment_values)
self.env['ir.asset'].create(asset_values)
def get_variable_values(self, url, bundle, variables):
content = self._read_scss(url, bundle)
return self._extract_variables(
content.decode('utf-8'), variables
)
def set_variable_values(self, url, bundle, variables):
original = self._read_scss(url, bundle).decode('utf-8')
content = self._update_variables(original, variables)
self._write_scss(url, bundle, content)
def reset_asset(self, url, bundle):
custom_url = self._get_custom_url(url, bundle)
self._find_attachment(custom_url).unlink()
self._find_asset(custom_url).unlink()

View File

@@ -0,0 +1,42 @@
from odoo import models
from odoo.http import request
class IrHttp(models.AbstractModel):
_inherit = "ir.http"
@classmethod
def _post_logout(cls):
super()._post_logout()
request.future_response.set_cookie('color_scheme', max_age=0)
def color_scheme(self):
cookie_scheme = request.httprequest.cookies.get('color_scheme')
scheme = cookie_scheme if cookie_scheme else super().color_scheme()
if user := request.env.user:
if user._is_public():
return super().color_scheme()
if user_scheme := user.res_users_settings_id.color_scheme:
if user_scheme in ('light', 'dark'):
return user_scheme
return scheme
def session_info(self):
result = super().session_info()
user = self.env.user
if user._is_internal():
for company in user.company_ids.with_context(bin_size=True):
result['user_companies']['allowed_companies'][company.id].update({
'has_background_image': bool(company.background_image),
'has_appsbar_image': bool(company.appbar_image),
})
result['chatter_position'] = user.chatter_position
result['dialog_size'] = user.dialog_size
result['pager_autoload_interval'] = int(
self.env['ir.config_parameter'].sudo().get_param(
'fusion_backend_theme.pager_autoload_interval',
default=30000
)
)
return result

View File

@@ -0,0 +1,19 @@
from odoo import models, fields
class ResCompany(models.Model):
_inherit = 'res.company'
favicon = fields.Binary(
string="Company Favicon",
attachment=True,
)
background_image = fields.Binary(
string='Apps Menu Background Image',
attachment=True,
)
appbar_image = fields.Binary(
string='Sidebar Logo Image',
attachment=True,
)

View File

@@ -0,0 +1,264 @@
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
# -----------------------------------------------------------------
# Properties
# -----------------------------------------------------------------
@property
def COLOR_FIELDS(self):
return [
'color_brand',
'color_primary',
'color_success',
'color_info',
'color_warning',
'color_danger',
]
@property
def THEME_COLOR_FIELDS(self):
return [
'color_appsmenu_text',
'color_sidebar_text',
'color_sidebar_active',
'color_sidebar_background',
]
@property
def COLOR_ASSET_LIGHT_URL(self):
return '/fusion_backend_theme/static/src/scss/colors_light.scss'
@property
def COLOR_BUNDLE_LIGHT_NAME(self):
return 'web._assets_primary_variables'
@property
def COLOR_ASSET_DARK_URL(self):
return '/fusion_backend_theme/static/src/scss/colors_dark.scss'
@property
def COLOR_BUNDLE_DARK_NAME(self):
return 'web.assets_web_dark'
@property
def COLOR_ASSET_THEME_URL(self):
return '/fusion_backend_theme/static/src/scss/colors_light.scss'
@property
def COLOR_BUNDLE_THEME_NAME(self):
return 'web._assets_primary_variables'
# -----------------------------------------------------------------
# Fields - Company Assets
# -----------------------------------------------------------------
theme_favicon = fields.Binary(
related='company_id.favicon',
readonly=False,
)
theme_background_image = fields.Binary(
related='company_id.background_image',
readonly=False,
)
appbar_image = fields.Binary(
related='company_id.appbar_image',
readonly=False,
)
# -----------------------------------------------------------------
# Fields - Light Mode Colors
# -----------------------------------------------------------------
color_brand_light = fields.Char(string='Brand Light Color')
color_primary_light = fields.Char(string='Primary Light Color')
color_success_light = fields.Char(string='Success Light Color')
color_info_light = fields.Char(string='Info Light Color')
color_warning_light = fields.Char(string='Warning Light Color')
color_danger_light = fields.Char(string='Danger Light Color')
# -----------------------------------------------------------------
# Fields - Dark Mode Colors
# -----------------------------------------------------------------
color_brand_dark = fields.Char(string='Brand Dark Color')
color_primary_dark = fields.Char(string='Primary Dark Color')
color_success_dark = fields.Char(string='Success Dark Color')
color_info_dark = fields.Char(string='Info Dark Color')
color_warning_dark = fields.Char(string='Warning Dark Color')
color_danger_dark = fields.Char(string='Danger Dark Color')
# -----------------------------------------------------------------
# Fields - Theme Colors
# -----------------------------------------------------------------
theme_color_appsmenu_text = fields.Char(string='App Switcher Text Color')
theme_color_sidebar_text = fields.Char(string='Sidebar Text Color')
theme_color_sidebar_active = fields.Char(string='Sidebar Active Color')
theme_color_sidebar_background = fields.Char(string='Sidebar Background Color')
# -----------------------------------------------------------------
# Light Color Helpers
# -----------------------------------------------------------------
def _get_light_color_values(self):
return self.env['fusion_backend_theme.color_editor'].get_variable_values(
self.COLOR_ASSET_LIGHT_URL,
self.COLOR_BUNDLE_LIGHT_NAME,
self.COLOR_FIELDS,
)
def _set_light_color_values(self, values):
colors = self._get_light_color_values()
for var, value in colors.items():
values[f'{var}_light'] = value
return values
def _detect_light_color_change(self):
colors = self._get_light_color_values()
return any(
val is not None and self[f'{var}_light'] != val
for var, val in colors.items()
)
def _replace_light_color_values(self):
variables = [
{'name': field, 'value': self[f'{field}_light']}
for field in self.COLOR_FIELDS
]
return self.env['fusion_backend_theme.color_editor'].set_variable_values(
self.COLOR_ASSET_LIGHT_URL,
self.COLOR_BUNDLE_LIGHT_NAME,
variables,
)
def _reset_light_color_assets(self):
self.env['fusion_backend_theme.color_editor'].reset_asset(
self.COLOR_ASSET_LIGHT_URL,
self.COLOR_BUNDLE_LIGHT_NAME,
)
# -----------------------------------------------------------------
# Dark Color Helpers
# -----------------------------------------------------------------
def _get_dark_color_values(self):
return self.env['fusion_backend_theme.color_editor'].get_variable_values(
self.COLOR_ASSET_DARK_URL,
self.COLOR_BUNDLE_DARK_NAME,
self.COLOR_FIELDS,
)
def _set_dark_color_values(self, values):
colors = self._get_dark_color_values()
for var, value in colors.items():
values[f'{var}_dark'] = value
return values
def _detect_dark_color_change(self):
colors = self._get_dark_color_values()
return any(
val is not None and self[f'{var}_dark'] != val
for var, val in colors.items()
)
def _replace_dark_color_values(self):
variables = [
{'name': field, 'value': self[f'{field}_dark']}
for field in self.COLOR_FIELDS
]
return self.env['fusion_backend_theme.color_editor'].set_variable_values(
self.COLOR_ASSET_DARK_URL,
self.COLOR_BUNDLE_DARK_NAME,
variables,
)
def _reset_dark_color_assets(self):
self.env['fusion_backend_theme.color_editor'].reset_asset(
self.COLOR_ASSET_DARK_URL,
self.COLOR_BUNDLE_DARK_NAME,
)
# -----------------------------------------------------------------
# Theme Color Helpers
# -----------------------------------------------------------------
def _get_theme_color_values(self):
return self.env['fusion_backend_theme.color_editor'].get_variable_values(
self.COLOR_ASSET_THEME_URL,
self.COLOR_BUNDLE_THEME_NAME,
self.THEME_COLOR_FIELDS,
)
def _set_theme_color_values(self, values):
colors = self._get_theme_color_values()
for var, value in colors.items():
values[f'theme_{var}'] = value
return values
def _detect_theme_color_change(self):
colors = self._get_theme_color_values()
return any(
val is not None and self[f'theme_{var}'] != val
for var, val in colors.items()
)
def _replace_theme_color_values(self):
variables = [
{'name': field, 'value': self[f'theme_{field}']}
for field in self.THEME_COLOR_FIELDS
]
return self.env['fusion_backend_theme.color_editor'].set_variable_values(
self.COLOR_ASSET_THEME_URL,
self.COLOR_BUNDLE_THEME_NAME,
variables,
)
def _reset_theme_color_assets(self):
self.env['fusion_backend_theme.color_editor'].reset_asset(
self.COLOR_ASSET_THEME_URL,
self.COLOR_BUNDLE_THEME_NAME,
)
# -----------------------------------------------------------------
# Actions
# -----------------------------------------------------------------
def action_reset_light_color_assets(self):
self._reset_light_color_assets()
return {'type': 'ir.actions.client', 'tag': 'reload'}
def action_reset_dark_color_assets(self):
self._reset_dark_color_assets()
return {'type': 'ir.actions.client', 'tag': 'reload'}
def action_reset_theme_color_assets(self):
self._reset_light_color_assets()
self._reset_dark_color_assets()
self._reset_theme_color_assets()
return {'type': 'ir.actions.client', 'tag': 'reload'}
# -----------------------------------------------------------------
# CRUD
# -----------------------------------------------------------------
def get_values(self):
res = super().get_values()
res = self._set_light_color_values(res)
res = self._set_dark_color_values(res)
res = self._set_theme_color_values(res)
return res
def set_values(self):
res = super().set_values()
if self._detect_light_color_change():
self._replace_light_color_values()
if self._detect_dark_color_change():
self._replace_dark_color_values()
if self._detect_theme_color_change():
self._replace_theme_color_values()
return res

View File

@@ -0,0 +1,57 @@
from odoo import fields, models
class ResUsers(models.Model):
_inherit = "res.users"
color_scheme = fields.Selection(
related="res_users_settings_id.color_scheme",
readonly=False,
)
sidebar_type = fields.Selection(
selection=[
('invisible', 'Invisible'),
('small', 'Small'),
('large', 'Large'),
],
string="Sidebar Type",
default='large',
required=True,
)
chatter_position = fields.Selection(
selection=[
('side', 'Side'),
('bottom', 'Bottom'),
],
string="Chatter Position",
default='side',
required=True,
)
dialog_size = fields.Selection(
selection=[
('minimize', 'Minimize'),
('maximize', 'Maximize'),
],
string="Dialog Size",
default='minimize',
required=True,
)
@property
def SELF_READABLE_FIELDS(self):
return super().SELF_READABLE_FIELDS + [
'color_scheme',
'sidebar_type',
'chatter_position',
'dialog_size',
]
@property
def SELF_WRITEABLE_FIELDS(self):
return super().SELF_WRITEABLE_FIELDS + [
'color_scheme',
'sidebar_type',
'chatter_position',
'dialog_size',
]

View File

@@ -0,0 +1,17 @@
from odoo import fields, models
class ResUsersSettings(models.Model):
_inherit = 'res.users.settings'
homemenu_config = fields.Json(
string="Home Menu Configuration",
readonly=True,
)
color_scheme = fields.Selection(
[("system", "System"), ("light", "Light"), ("dark", "Dark")],
default="system",
required=True,
string="Color Scheme",
)