diff --git a/odex30_base/expert_theme/COLOR_CUSTOMIZATION.md b/odex30_base/expert_theme/COLOR_CUSTOMIZATION.md
new file mode 100644
index 0000000..0347cc0
--- /dev/null
+++ b/odex30_base/expert_theme/COLOR_CUSTOMIZATION.md
@@ -0,0 +1,176 @@
+# Expert Theme - Color Customization Guide
+
+## 🎨 Overview
+
+The Expert Theme includes a comprehensive color customization system that allows you to easily change the appearance of your Odoo backend. You can customize colors for:
+
+- **Navbar** (top navigation bar)
+- **Buttons** (Activate, Upgrade, Learn More, etc.)
+- **Sidebar** (left navigation menu)
+- **Backgrounds** (page background, content areas)
+- **Text** (headings, body text, links)
+- **Status colors** (success, warning, error, info)
+
+## 🚀 Quick Start
+
+### Method 1: Edit Configuration File (Recommended)
+
+1. Open the file: `static/src/css/expert_theme_config.css`
+2. Find the color you want to change in the `:root` section
+3. Replace the hex color code with your desired color
+4. Save the file and refresh your Odoo page
+
+### Method 2: Use Pre-made Themes
+
+The configuration file includes example color schemes. To use them:
+
+1. Open `static/src/css/expert_theme_config.css`
+2. Find the theme you want (Blue, Green, or Dark)
+3. Uncomment the theme section (remove `/*` and `*/`)
+4. Comment out the default theme section
+5. Save and refresh
+
+## 🎯 Color Variables Reference
+
+### Primary Colors
+```css
+--expert-primary-color: #875A7B; /* Main brand color */
+--expert-primary-hover: #6B4C6B; /* Hover state */
+--expert-primary-light: #A67B9B; /* Light version */
+```
+
+### Secondary Colors
+```css
+--expert-secondary-color: #00A09D; /* Secondary brand color */
+--expert-secondary-hover: #008B8A; /* Secondary hover */
+```
+
+### Button Colors
+```css
+--expert-btn-primary: #875A7B; /* Primary buttons (Activate) */
+--expert-btn-secondary: #00A09D; /* Secondary buttons (Upgrade) */
+--expert-btn-light: #F8F9FA; /* Light buttons (Learn More) */
+```
+
+### Background Colors
+```css
+--expert-bg-primary: #875A7B; /* Navbar background */
+--expert-bg-secondary: #F8F9FA; /* Sidebar background */
+--expert-bg-content: #FFFFFF; /* Content background */
+--expert-bg-gradient-start: #f5f7fa; /* Page gradient start */
+--expert-bg-gradient-end: #c3cfe2; /* Page gradient end */
+```
+
+### Text Colors
+```css
+--expert-text-primary: #212529; /* Main text */
+--expert-text-secondary: #6C757D; /* Secondary text */
+--expert-text-light: #FFFFFF; /* Light text */
+```
+
+### Status Colors
+```css
+--expert-success: #28A745; /* Success/Active */
+--expert-warning: #FFC107; /* Warning */
+--expert-danger: #DC3545; /* Error/Danger */
+--expert-info: #17A2B8; /* Info */
+```
+
+## 🎨 Example Color Schemes
+
+### Blue Theme
+```css
+:root {
+ --expert-primary-color: #007BFF;
+ --expert-primary-hover: #0056B3;
+ --expert-secondary-color: #6F42C1;
+ --expert-bg-primary: #007BFF;
+ --expert-bg-gradient-start: #E3F2FD;
+ --expert-bg-gradient-end: #BBDEFB;
+}
+```
+
+### Green Theme
+```css
+:root {
+ --expert-primary-color: #28A745;
+ --expert-primary-hover: #1E7E34;
+ --expert-secondary-color: #20C997;
+ --expert-bg-primary: #28A745;
+ --expert-bg-gradient-start: #E8F5E8;
+ --expert-bg-gradient-end: #C3E6C3;
+}
+```
+
+### Dark Theme
+```css
+:root {
+ --expert-primary-color: #343A40;
+ --expert-primary-hover: #23272B;
+ --expert-secondary-color: #6C757D;
+ --expert-bg-primary: #343A40;
+ --expert-bg-secondary: #495057;
+ --expert-bg-content: #F8F9FA;
+ --expert-bg-gradient-start: #2C3E50;
+ --expert-bg-gradient-end: #34495E;
+ --expert-text-primary: #FFFFFF;
+ --expert-text-secondary: #E9ECEF;
+}
+```
+
+## 🔧 Advanced Customization
+
+### Custom Color Combinations
+
+You can create your own color schemes by:
+
+1. **Choosing a primary color** - This will be your main brand color
+2. **Creating a hover color** - Usually a darker shade of your primary
+3. **Selecting a secondary color** - A complementary color for variety
+4. **Setting background colors** - Light colors for readability
+5. **Choosing text colors** - High contrast for accessibility
+
+### Color Tools
+
+Use these tools to help choose colors:
+
+- **Color Picker**: Use your browser's developer tools
+- **Color Palettes**: [Coolors.co](https://coolors.co), [Adobe Color](https://color.adobe.com)
+- **Accessibility**: [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
+
+## 📝 Tips
+
+1. **Test your colors** - Always preview changes in your browser
+2. **Consider accessibility** - Ensure good contrast between text and backgrounds
+3. **Keep it consistent** - Use the same color family throughout
+4. **Start simple** - Begin with just changing the primary color
+5. **Backup your changes** - Save a copy of your working configuration
+
+## 🐛 Troubleshooting
+
+### Colors not changing?
+- Make sure you saved the file
+- Clear your browser cache
+- Check for syntax errors in the CSS
+- Verify the file is being loaded (check browser developer tools)
+
+### Colors look wrong?
+- Check for typos in color codes
+- Ensure you're using valid hex codes (e.g., #FF0000)
+- Make sure you're editing the right file
+
+### Need to reset?
+- Restore the original `expert_theme_config.css` file
+- Or copy the default values from the documentation above
+
+## 📞 Support
+
+If you need help with color customization, check:
+1. This documentation
+2. The example themes in the config file
+3. Browser developer tools for debugging
+4. Odoo community forums for additional help
+
+---
+
+**Happy Theming! 🎨**
diff --git a/odex30_base/expert_theme/README.md b/odex30_base/expert_theme/README.md
new file mode 100644
index 0000000..b50cee3
--- /dev/null
+++ b/odex30_base/expert_theme/README.md
@@ -0,0 +1,30 @@
+# Expert Theme
+
+A simple and clean theme for Odoo 18 backend that provides a custom home page displaying only installed modules.
+
+## Features
+
+- **Custom Home Page**: Clean interface showing only your installed modules
+- **Simple Navigation**: Easy access to all your applications
+- **Responsive Design**: Works on all devices
+- **Minimal Interface**: No clutter, just functionality
+
+## Installation
+
+1. Copy this module to your Odoo addons directory
+2. Update the apps list in Odoo
+3. Install the "Expert Theme" module
+4. The custom home page will be available after installation
+
+## Usage
+
+After installation, when you log into Odoo, you'll see the new home page with all your installed modules displayed in a clean grid layout. Simply click on any module to access it.
+
+## Requirements
+
+- Odoo 18.0
+- No additional dependencies
+
+## Support
+
+This is a simple theme module designed for basic customization of the Odoo backend home page.
diff --git a/odex30_base/expert_theme/TESTING_GUIDE.md b/odex30_base/expert_theme/TESTING_GUIDE.md
new file mode 100644
index 0000000..b358669
--- /dev/null
+++ b/odex30_base/expert_theme/TESTING_GUIDE.md
@@ -0,0 +1,312 @@
+# Testing Guide - Login Page Templates Feature
+
+## Prerequisites
+- Odoo 18 installed and running
+- Expert Theme module installed
+- Admin access to Odoo backend
+
+---
+
+## Step 1: Install/Upgrade the Module
+
+1. **Go to Apps Menu**
+ - Navigate to: `Apps` → Search for "Expert Theme"
+ - Click on the module
+
+2. **Upgrade the Module**
+ - Click the "Upgrade" button (if already installed)
+ - OR click "Install" if not installed yet
+ - Wait for the upgrade/installation to complete
+
+3. **Verify Installation**
+ - Check that no errors appear in the logs
+ - The module should show as "Installed"
+
+---
+
+## Step 2: Access Login Template Configuration
+
+1. **Navigate to Configuration**
+ - Go to: `Expert Home` → `Login Page Templates`
+ - OR use the menu: `Expert Home` → `Login Page Templates` (should be above "Theme Colors")
+
+2. **Verify Default Templates**
+ - You should see **2 templates** created by default:
+ - **Default Template** (White background #FFFFFF) - Should be **Active**
+ - **Light Gray Template** (Gray background #F5F5F5) - Should be **Inactive**
+
+---
+
+## Step 3: Test Template Switching
+
+### Test 3.1: Switch to Gray Template
+
+1. **Open the Gray Template**
+ - Click on "Light Gray Template" from the list
+
+2. **Activate the Template**
+ - Click the "Activate" button in the form header
+ - OR toggle the "Active" field to `True`
+ - Click "Save"
+
+3. **Verify Switch**
+ - The "Default Template" should automatically become **Inactive**
+ - Only "Light Gray Template" should be **Active**
+ - Check the list view - only one template should have the Active toggle ON
+
+### Test 3.2: Switch Back to Default Template
+
+1. **Open Default Template**
+ - Click on "Default Template" from the list
+
+2. **Activate it**
+ - Click "Activate" button or toggle "Active" field
+ - Click "Save"
+
+3. **Verify**
+ - "Light Gray Template" should become inactive
+ - "Default Template" should be active
+
+---
+
+## Step 4: Test Login Page Appearance
+
+### Test 4.1: Test with Default Template (White)
+
+1. **Logout from Odoo**
+ - Click on your user menu (top right)
+ - Click "Log out"
+
+2. **Check Login Page**
+ - The login page should have a **white background** (#FFFFFF)
+ - All login form elements should be visible and functional
+
+3. **Verify JavaScript Loading**
+ - Open browser Developer Tools (F12)
+ - Go to Console tab
+ - Check for any errors
+ - You should see the CSS variable being set: `--expert-login-bg-color`
+
+### Test 4.2: Test with Gray Template
+
+1. **Switch to Gray Template** (from Step 3.1)
+
+2. **Logout and Check Login Page**
+ - Logout from Odoo
+ - The login page should now have a **light gray background** (#F5F5F5)
+ - Login form should still be visible and functional
+
+3. **Verify Background Applied**
+ - Check browser DevTools → Elements tab
+ - Inspect `
` or `` element
+ - Should have `background-color: #F5F5F5` applied
+
+---
+
+## Step 5: Test Color Customization
+
+### Test 5.1: Edit Template Background Color
+
+1. **Edit a Template**
+ - Go to `Login Page Templates`
+ - Open any template (e.g., "Default Template")
+
+2. **Change Background Color**
+ - Click on the "Background Color" field
+ - Choose a different color (e.g., `#E3F2FD` - light blue)
+ - Click "Save"
+
+3. **Test on Login Page**
+ - Logout and check login page
+ - Background should be the new color you selected
+
+### Test 5.2: Create New Template
+
+1. **Create New Template**
+ - Go to `Login Page Templates`
+ - Click "Create" button
+
+2. **Fill Template Details**
+ - Name: "Blue Template"
+ - Background Color: `#2196F3` (or any blue color)
+ - Description: "Blue themed login page"
+ - Sequence: 3
+ - **DO NOT** activate it yet
+
+3. **Save and Activate**
+ - Click "Save"
+ - Then click "Activate" button
+ - Verify other templates become inactive
+
+4. **Test on Login Page**
+ - Logout and verify blue background appears
+
+---
+
+## Step 6: Test Edge Cases
+
+### Test 6.1: Multiple Templates (Only One Active)
+
+1. **Create Multiple Templates**
+ - Create 2-3 templates
+ - Try to activate multiple templates at once
+
+2. **Expected Behavior**
+ - Only the last activated template should be active
+ - All others should automatically become inactive
+ - This ensures only one template is applied at a time
+
+### Test 6.2: No Active Template
+
+1. **Deactivate All Templates**
+ - Go to all templates and set `Active = False`
+ - Save all
+
+2. **Check Login Page**
+ - Logout and check login page
+ - Should still work (fallback to default white or system default)
+ - OR a default template should be auto-created
+
+### Test 6.3: Invalid Color Code
+
+1. **Test Invalid Color**
+ - Edit a template
+ - Enter invalid color code (e.g., "invalid" or "12345")
+ - Save
+
+2. **Expected Behavior**
+ - Should either:
+ - Show validation error
+ - OR fallback to default color (#FFFFFF)
+
+---
+
+## Step 7: Test API Endpoint
+
+### Test 7.1: Check API Response
+
+1. **Open Browser DevTools**
+ - Press F12
+ - Go to Network tab
+
+2. **Access Login Page**
+ - Go to login page (while logged out)
+
+3. **Check API Call**
+ - Look for request: `/expert_theme/get_login_template_styles`
+ - Click on it to see response
+ - Should return JSON:
+ ```json
+ {
+ "success": true,
+ "styles": {
+ "background_color": "#F5F5F5"
+ }
+ }
+ ```
+
+4. **Verify Public Access**
+ - The API should work without authentication (public access)
+ - Try accessing directly: `http://your-odoo-url/expert_theme/get_login_template_styles`
+
+---
+
+## Step 8: Test Browser Compatibility
+
+Test the login page in different browsers:
+- ✅ Chrome/Edge (Chromium)
+- ✅ Firefox
+- ✅ Safari (if available)
+
+Verify that:
+- Background colors apply correctly
+- Login form remains functional
+- No JavaScript errors in console
+
+---
+
+## Step 9: Performance Testing
+
+1. **Check Page Load Time**
+ - Login page should load quickly
+ - API call should be fast (< 100ms ideally)
+
+2. **Check for Console Errors**
+ - Open DevTools → Console
+ - Should see no errors
+ - Only warnings are acceptable (e.g., if API fails, it should gracefully handle)
+
+---
+
+## Step 10: Final Verification Checklist
+
+- [ ] Module installs/upgrades without errors
+- [ ] Menu "Login Page Templates" appears in Expert Home
+- [ ] Default templates are created (2 templates)
+- [ ] Can switch between templates
+- [ ] Only one template can be active at a time
+- [ ] Login page background changes when template is switched
+- [ ] Can edit template colors
+- [ ] Can create new templates
+- [ ] Login form remains functional with all templates
+- [ ] API endpoint works (public access)
+- [ ] No JavaScript errors in console
+- [ ] Works in multiple browsers
+
+---
+
+## Troubleshooting
+
+### Issue: Login page background doesn't change
+
+**Solutions:**
+1. Clear browser cache (Ctrl+Shift+Delete)
+2. Hard refresh login page (Ctrl+F5)
+3. Check browser console for JavaScript errors
+4. Verify API endpoint is accessible: `/expert_theme/get_login_template_styles`
+5. Check that a template is actually active in the backend
+
+### Issue: Menu "Login Page Templates" not visible
+
+**Solutions:**
+1. Upgrade the module again
+2. Check user permissions (should be available to all users)
+3. Clear browser cache
+4. Restart Odoo server
+
+### Issue: Multiple templates active at once
+
+**Solutions:**
+1. This shouldn't happen - check the model's `write()` method
+2. Manually deactivate all except one
+3. Report as a bug if it persists
+
+### Issue: API returns error
+
+**Solutions:**
+1. Check Odoo logs for Python errors
+2. Verify the model `expert.login.template` exists
+3. Check database for template records
+4. Verify controller route is registered
+
+---
+
+## Expected Results Summary
+
+✅ **Success Criteria:**
+- Admin can configure login page templates from backend
+- Can switch between templates easily
+- Login page background changes immediately after switching
+- Only one template active at a time
+- Login functionality remains intact
+- No errors in console or logs
+
+---
+
+## Notes
+
+- The feature is designed to be simple initially (only background color)
+- Future enhancements can add more customization options
+- All changes take effect immediately (no server restart needed)
+- Templates are stored in database, so they persist after module upgrades
+
diff --git a/odex30_base/expert_theme/__init__.py b/odex30_base/expert_theme/__init__.py
new file mode 100644
index 0000000..c3d410e
--- /dev/null
+++ b/odex30_base/expert_theme/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import models
+from . import controllers
diff --git a/odex30_base/expert_theme/__manifest__.py b/odex30_base/expert_theme/__manifest__.py
new file mode 100644
index 0000000..fd5b3b7
--- /dev/null
+++ b/odex30_base/expert_theme/__manifest__.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': 'Expert Theme',
+ 'version': '18.0.1.0.0',
+ 'category': 'Theme/Backend',
+ 'summary': 'Custom backend theme with installed modules home page',
+ 'description': """
+ Expert Theme
+ ============
+
+ A simple custom theme for Odoo 18 backend that provides:
+ - Custom home page displaying only installed modules
+ - Clean and minimal interface
+ - Easy navigation to installed applications
+ """,
+ 'author': 'Expert',
+ 'website': '',
+ 'depends': ['base', 'web'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'data/expert_login_template_data.xml',
+ 'views/login_templates.xml',
+ 'views/expert_login_template_views.xml',
+ 'views/expert_theme_config_views.xml',
+ 'views/expert_home_views.xml',
+ 'views/expert_menu_views.xml',
+ ],
+ 'assets': {
+ 'web.assets_backend': [
+ 'expert_theme/static/src/css/expert_theme_config.css',
+ 'expert_theme/static/src/css/expert_theme.css',
+ 'expert_theme/static/src/js/expert_theme_dynamic.js',
+ 'expert_theme/static/src/js/expert_theme_config.js',
+ 'expert_theme/static/src/js/expert_home.js',
+ 'expert_theme/static/src/js/expert_login_template_list.js',
+ 'expert_theme/static/src/xml/expert_home.xml',
+ ],
+ 'web.assets_frontend': [
+ 'expert_theme/static/src/css/expert_login.css',
+ 'expert_theme/static/src/css/login_modern.css',
+ 'expert_theme/static/src/css/login_minimal.css',
+ 'expert_theme/static/src/js/expert_login_template.js',
+ ],
+ },
+ 'installable': True,
+ 'auto_install': False,
+ 'application': False,
+ 'license': 'LGPL-3',
+}
diff --git a/odex30_base/expert_theme/controllers/__init__.py b/odex30_base/expert_theme/controllers/__init__.py
new file mode 100644
index 0000000..2b4d723
--- /dev/null
+++ b/odex30_base/expert_theme/controllers/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import expert_controller
diff --git a/odex30_base/expert_theme/controllers/expert_controller.py b/odex30_base/expert_theme/controllers/expert_controller.py
new file mode 100644
index 0000000..4f5b646
--- /dev/null
+++ b/odex30_base/expert_theme/controllers/expert_controller.py
@@ -0,0 +1,286 @@
+# -*- coding: utf-8 -*-
+
+from odoo import http, fields
+from odoo.http import request
+import json
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+class ExpertController(http.Controller):
+
+ @http.route('/expert_theme/test', type='http', auth='user')
+ def test_controller(self):
+ """Simple test endpoint to verify controller is working"""
+ return request.make_json_response({
+ 'success': True,
+ 'message': 'Controller is working!',
+ 'timestamp': fields.Datetime.now()
+ })
+
+ @http.route('/expert_theme/get_css_variables', type='http', auth='user')
+ def get_css_variables(self):
+ """Get CSS variables for the active theme configuration"""
+ try:
+ config = request.env['expert.theme.config'].get_active_config()
+ css_variables = config.get_css_variables()
+ return request.make_json_response({
+ 'success': True,
+ 'css_variables': css_variables
+ })
+ except Exception as e:
+ return request.make_json_response({
+ 'success': False,
+ 'error': str(e)
+ })
+
+ @http.route('/expert_theme/apply_theme', type='http', auth='user', methods=['POST'])
+ def apply_theme(self):
+ """Apply theme changes immediately"""
+ try:
+ # Get the active configuration
+ config = request.env['expert.theme.config'].get_active_config()
+
+ # Apply the theme (this could trigger cache invalidation, etc.)
+ config.apply_theme()
+
+ return request.make_json_response({
+ 'success': True,
+ 'message': 'Theme applied successfully!'
+ })
+ except Exception as e:
+ return request.make_json_response({
+ 'success': False,
+ 'error': str(e)
+ })
+
+ @http.route('/web/login', type='http', auth='none', methods=['GET', 'POST'], csrf=False)
+ def web_login(self, redirect=None, **kw):
+ """Override Odoo login to use expert login templates when configured.
+
+ - POST: delegate to standard Odoo login logic (authentication, redirects, etc.).
+ - GET: render the active expert login template if not 'default', otherwise use standard login.
+ """
+ # Import Home from its new location in Odoo 18
+ from odoo.addons.web.controllers.home import Home
+
+ # Handle login submission via standard controller
+ if request.httprequest.method == 'POST':
+ home = Home()
+ return home.web_login(redirect=redirect, **kw)
+
+ # GET: decide which template to render
+ try:
+ login_template = request.env['expert.login.template'].sudo().get_active_template()
+ template_name = login_template.get_template_name()
+ except Exception as e:
+ _logger.error("Error getting active login template: %s", e)
+ template_name = 'web.login'
+
+ # For default template, just call the standard login
+ if template_name == 'web.login':
+ home = Home()
+ return home.web_login(redirect=redirect, **kw)
+
+ # Try to render the selected expert template; fall back to default on error
+ try:
+ return request.render(template_name, {
+ 'redirect': redirect or '',
+ 'login': kw.get('login', ''),
+ 'login_template': login_template,
+ })
+ except Exception as e:
+ _logger.error("Error rendering login template '%s': %s", template_name, e)
+ home = Home()
+ return home.web_login(redirect=redirect, **kw)
+
+ @http.route('/expert_theme/get_login_template_styles', type='http', auth='public', methods=['GET'])
+ def get_login_template_styles(self):
+ """Get CSS styles for the active login template (public access for login page)"""
+ try:
+ template = request.env['expert.login.template'].sudo().get_active_template()
+ styles = template.get_template_styles()
+ return request.make_json_response({
+ 'success': True,
+ 'styles': styles
+ })
+ except Exception as e:
+ # Return default styles if error
+ return request.make_json_response({
+ 'success': False,
+ 'error': str(e),
+ 'styles': {'background_color': '#FFFFFF'}
+ })
+
+ @http.route('/expert_theme/get_installed_modules_http', type='http', auth='user')
+ def get_installed_modules_http(self):
+ """Return list of installed modules for the home page via HTTP"""
+ try:
+ # Get all installed modules
+ installed_modules = request.env['ir.module.module'].search([
+ ('state', '=', 'installed'),
+ ('name', '!=', 'expert_theme') # Exclude our own module
+ ])
+
+ # Get all top-level menu items that belong to installed modules
+ menu_items = request.env['ir.ui.menu'].search([
+ ('parent_id', '=', False), # Top level menus only
+ ('active', '=', True),
+ ('name', 'not in', ['Expert Home', 'Dashboard']) # Exclude our own menus
+ ])
+
+ result = []
+ installed_module_names = installed_modules.mapped('name')
+
+ debug_info = {
+ 'total_installed_modules': len(installed_modules),
+ 'total_menu_items': len(menu_items)
+ }
+
+ for menu in menu_items:
+ # Check if this menu belongs to an installed module
+ module_name = None
+ if menu.web_icon and ',' in menu.web_icon:
+ module_name = menu.web_icon.split(',')[0]
+
+ # Include menu if it belongs to an installed module OR if it has no web_icon (base modules)
+ if (module_name and module_name in installed_module_names) or not menu.web_icon:
+
+ # Create URL based on action type - use proper Odoo navigation
+ if menu.action and menu.action.type == 'ir.actions.act_window':
+ # For act_window, use the action ID
+ url = f'/web#action={menu.action.id}'
+ elif menu.action and menu.action.type == 'ir.actions.client':
+ # For client actions, use the action ID
+ url = f'/web#action={menu.action.id}'
+ elif menu.action and menu.action.type == 'ir.actions.server':
+ # For server actions, use the action ID
+ url = f'/web#action={menu.action.id}'
+ else:
+ # For menus without actions, use menu_id
+ url = f'/web#menu_id={menu.id}'
+
+ result.append({
+ 'id': menu.id,
+ 'name': menu.name,
+ 'web_icon': menu.web_icon or 'base,static/description/icon.png',
+ 'action': menu.action.id if menu.action else False,
+ 'url': url,
+ 'module_name': module_name
+ })
+
+ return request.make_json_response({
+ 'success': True,
+ 'modules': result,
+ 'debug_info': debug_info
+ })
+ except Exception as e:
+ return request.make_json_response({
+ 'success': False,
+ 'error': str(e)
+ })
+
+ @http.route('/web/signup', type='http', auth='public', methods=['GET', 'POST'], website=True, csrf=False)
+ def web_auth_signup(self, redirect=None, **kw):
+ """Override Odoo signup to use expert signup templates when modern template is active.
+
+ - POST: delegate to standard Odoo signup logic (authentication, redirects, etc.).
+ - GET: render the active expert signup template if modern template is active, otherwise use standard signup.
+ """
+ # Import AuthSignupHome from auth_signup module
+ try:
+ from odoo.addons.auth_signup.controllers.main import AuthSignupHome
+ except ImportError:
+ # If auth_signup is not installed, return 404
+ from werkzeug.exceptions import NotFound
+ raise NotFound()
+
+ # Handle signup submission via standard controller
+ if request.httprequest.method == 'POST':
+ auth_signup_home = AuthSignupHome()
+ return auth_signup_home.web_auth_signup(redirect=redirect, **kw)
+
+ # GET: decide which template to render
+ try:
+ login_template = request.env['expert.login.template'].sudo().get_active_template()
+ template_name = login_template.get_signup_template_name()
+ except Exception as e:
+ _logger.error("Error getting active signup template: %s", e)
+ template_name = 'auth_signup.signup'
+
+ # For default template, just call the standard signup
+ if template_name == 'auth_signup.signup':
+ auth_signup_home = AuthSignupHome()
+ return auth_signup_home.web_auth_signup(redirect=redirect, **kw)
+
+ # Try to render the selected expert template; fall back to default on error
+ try:
+ # Get signup context from auth_signup
+ auth_signup_home = AuthSignupHome()
+ qcontext = auth_signup_home.get_auth_signup_qcontext()
+ qcontext.update({
+ 'redirect': redirect or '',
+ 'login': kw.get('login', ''),
+ 'login_template': login_template,
+ })
+ response = request.render(template_name, qcontext)
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN'
+ response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
+ return response
+ except Exception as e:
+ _logger.error("Error rendering signup template '%s': %s", template_name, e)
+ auth_signup_home = AuthSignupHome()
+ return auth_signup_home.web_auth_signup(redirect=redirect, **kw)
+
+ @http.route('/web/reset_password', type='http', auth='public', methods=['GET', 'POST'], website=True, csrf=False)
+ def web_auth_reset_password(self, redirect=None, **kw):
+ """Override Odoo reset password to use expert reset password templates when modern template is active.
+
+ - POST: delegate to standard Odoo reset password logic (authentication, redirects, etc.).
+ - GET: render the active expert reset password template if modern template is active, otherwise use standard reset password.
+ """
+ # Import AuthSignupHome from auth_signup module
+ try:
+ from odoo.addons.auth_signup.controllers.main import AuthSignupHome
+ except ImportError:
+ # If auth_signup is not installed, return 404
+ from werkzeug.exceptions import NotFound
+ raise NotFound()
+
+ # Handle reset password submission via standard controller
+ if request.httprequest.method == 'POST':
+ auth_signup_home = AuthSignupHome()
+ return auth_signup_home.web_auth_reset_password(redirect=redirect, **kw)
+
+ # GET: decide which template to render
+ try:
+ login_template = request.env['expert.login.template'].sudo().get_active_template()
+ template_name = login_template.get_reset_password_template_name()
+ except Exception as e:
+ _logger.error("Error getting active reset password template: %s", e)
+ template_name = 'auth_signup.reset_password'
+
+ # For default template, just call the standard reset password
+ if template_name == 'auth_signup.reset_password':
+ auth_signup_home = AuthSignupHome()
+ return auth_signup_home.web_auth_reset_password(redirect=redirect, **kw)
+
+ # Try to render the selected expert template; fall back to default on error
+ try:
+ # Get reset password context from auth_signup
+ auth_signup_home = AuthSignupHome()
+ qcontext = auth_signup_home.get_auth_signup_qcontext()
+ qcontext.update({
+ 'redirect': redirect or '',
+ 'login': kw.get('login', ''),
+ 'login_template': login_template,
+ })
+ response = request.render(template_name, qcontext)
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN'
+ response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
+ return response
+ except Exception as e:
+ _logger.error("Error rendering reset password template '%s': %s", template_name, e)
+ auth_signup_home = AuthSignupHome()
+ return auth_signup_home.web_auth_reset_password(redirect=redirect, **kw)
diff --git a/odex30_base/expert_theme/data/expert_login_template_data.xml b/odex30_base/expert_theme/data/expert_login_template_data.xml
new file mode 100644
index 0000000..dfd3b38
--- /dev/null
+++ b/odex30_base/expert_theme/data/expert_login_template_data.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+ Default Template
+ True
+ 1
+ default
+ #FFFFFF
+ True
+ Standard Odoo login page template
+
+
+
+
+ Modern Template
+ False
+ 2
+ modern
+ #667eea
+ False
+ Modern card design with gradient background
+
+
+
+
+ Minimal Template
+ False
+ 3
+ minimal
+ #F8F9FA
+ False
+ Clean minimal design template
+
+
+
+
+ Corporate Template
+ False
+ 4
+ corporate
+ #1a1a1a
+ False
+ Professional corporate dark theme
+
+
+
+
diff --git a/odex30_base/expert_theme/models/__init__.py b/odex30_base/expert_theme/models/__init__.py
new file mode 100644
index 0000000..6878437
--- /dev/null
+++ b/odex30_base/expert_theme/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import expert_theme_config
+from . import expert_login_template
diff --git a/odex30_base/expert_theme/models/expert_login_template.py b/odex30_base/expert_theme/models/expert_login_template.py
new file mode 100644
index 0000000..a17dce2
--- /dev/null
+++ b/odex30_base/expert_theme/models/expert_login_template.py
@@ -0,0 +1,289 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+
+
+class ExpertLoginTemplate(models.Model):
+ _name = 'expert.login.template'
+ _description = 'Expert Login Page Template'
+ _rec_name = 'name'
+ _order = 'sequence, name'
+
+ name = fields.Char(string='Template Name', required=True, help='Name of the login page template')
+ active = fields.Boolean(string='Active', default=False, help='Only one template can be active at a time. Activate this template to use it on the login page.')
+ sequence = fields.Integer(string='Sequence', default=10, help='Order of display')
+
+ # Template Design Selection
+ template_type = fields.Selection([
+ ('default', 'Default Odoo Login'),
+ ('modern', 'Modern Card Design'),
+ ('minimal', 'Minimal Design'),
+ ('corporate', 'Corporate Design'),
+ ], string='Template Design', default='default', required=True,
+ help='Select the login page template design. Each design has a completely different HTML structure and layout.')
+
+ # Template Settings (for backward compatibility and CSS customization)
+ background_color = fields.Char(string='Background Color', default='#FFFFFF', help='Background color for the login page')
+ is_default = fields.Boolean(string='Is Default Template', default=False, help='Default template to use')
+
+ # Template Images
+ modern_template_image = fields.Binary(string='Modern Template Image', help='Image to display on the right side of Modern login, signup, and reset password pages')
+ minimal_template_image = fields.Binary(string='Minimal Template Image', help='Image to display on the right side of Minimal login, signup, and reset password pages')
+ corporate_template_image = fields.Binary(string='Corporate Template Image', help='Image to display on the right side of Corporate login, signup, and reset password pages')
+
+ # Template Logos
+ modern_template_logo = fields.Binary(string='Modern Template Logo', help='Company logo to display on Modern login, signup, and reset password pages')
+ minimal_template_logo = fields.Binary(string='Minimal Template Logo', help='Company logo to display on Minimal login, signup, and reset password pages')
+ corporate_template_logo = fields.Binary(string='Corporate Template Logo', help='Company logo to display on Corporate login, signup, and reset password pages')
+
+ # Modern Template Text Fields
+ modern_login_title = fields.Char(string='Login Title', default='Welcome to Expert 👋', help='Title text for Modern template login page')
+ modern_login_subtitle = fields.Char(string='Login Subtitle', default='Kindly fill in your details below to sign in to your account', help='Subtitle text for Modern template login page')
+ modern_login_button_text = fields.Char(string='Login Button Text', default='Sign In', help='Button text for Modern template login page')
+ modern_login_button_bg_color = fields.Char(string='Login Button Background', default='#007bff', help='Background color for login button')
+ modern_login_button_text_color = fields.Char(string='Login Button Text Color', default='#FFFFFF', help='Text color for login button')
+ modern_login_button_bg_hover = fields.Char(string='Login Button BG Hover', default='#0056b3', help='Background color for login button on hover')
+ modern_login_button_text_hover = fields.Char(string='Login Button Text Hover', default='#FFFFFF', help='Text color for login button on hover')
+
+ modern_signup_title = fields.Char(string='Signup Title', default='Create an account', help='Title text for Modern template signup page')
+ modern_signup_subtitle = fields.Char(string='Signup Subtitle', default='Join us today and get started', help='Subtitle text for Modern template signup page')
+ modern_signup_button_text = fields.Char(string='Signup Button Text', default='Create an account', help='Button text for Modern template signup page')
+ modern_signup_button_bg_color = fields.Char(string='Signup Button Background', default='#28a745', help='Background color for signup button')
+ modern_signup_button_text_color = fields.Char(string='Signup Button Text Color', default='#FFFFFF', help='Text color for signup button')
+ modern_signup_button_bg_hover = fields.Char(string='Signup Button BG Hover', default='#218838', help='Background color for signup button on hover')
+ modern_signup_button_text_hover = fields.Char(string='Signup Button Text Hover', default='#FFFFFF', help='Text color for signup button on hover')
+
+ modern_reset_title = fields.Char(string='Reset Password Title', default='Reset your password', help='Title text for Modern template reset password page')
+ modern_reset_subtitle = fields.Char(string='Reset Password Subtitle', default='Enter your email to receive reset instructions', help='Subtitle text for Modern template reset password page')
+
+ # Minimal Template Text Fields
+ minimal_login_title = fields.Char(string='Login Title', default='Welcome to Expert 👋', help='Title text for Minimal template login page')
+ minimal_login_subtitle = fields.Char(string='Login Subtitle', default='Kindly fill in your details below to sign in to your account', help='Subtitle text for Minimal template login page')
+ minimal_login_button_text = fields.Char(string='Login Button Text', default='Sign In', help='Button text for Minimal template login page')
+ minimal_login_button_bg_color = fields.Char(string='Login Button Background', default='#E5E5E5', help='Background color for login button')
+ minimal_login_button_text_color = fields.Char(string='Login Button Text Color', default='#000000', help='Text color for login button')
+ minimal_login_button_bg_hover = fields.Char(string='Login Button BG Hover', default='#D0D0D0', help='Background color for login button on hover')
+ minimal_login_button_text_hover = fields.Char(string='Login Button Text Hover', default='#000000', help='Text color for login button on hover')
+
+ minimal_signup_title = fields.Char(string='Signup Title', default='Create an account', help='Title text for Minimal template signup page')
+ minimal_signup_subtitle = fields.Char(string='Signup Subtitle', default='Join us today and get started', help='Subtitle text for Minimal template signup page')
+ minimal_signup_button_text = fields.Char(string='Signup Button Text', default='Create an account', help='Button text for Minimal template signup page')
+ minimal_signup_button_bg_color = fields.Char(string='Signup Button Background', default='#000000', help='Background color for signup button')
+ minimal_signup_button_text_color = fields.Char(string='Signup Button Text Color', default='#FFFFFF', help='Text color for signup button')
+ minimal_signup_button_bg_hover = fields.Char(string='Signup Button BG Hover', default='#333333', help='Background color for signup button on hover')
+ minimal_signup_button_text_hover = fields.Char(string='Signup Button Text Hover', default='#FFFFFF', help='Text color for signup button on hover')
+
+ minimal_reset_title = fields.Char(string='Reset Password Title', default='Reset your password', help='Title text for Minimal template reset password page')
+ minimal_reset_subtitle = fields.Char(string='Reset Password Subtitle', default='Enter your email to receive reset instructions', help='Subtitle text for Minimal template reset password page')
+
+ # Description
+ description = fields.Text(string='Description', help='Description of this template')
+
+ @api.model
+ def get_active_template(self):
+ """Get the active login template or return default"""
+ try:
+ template = self.search([('active', '=', True)], limit=1, order='sequence')
+ if not template:
+ # Create a default template if none exists
+ template = self.create({
+ 'name': 'Default Template',
+ 'active': True,
+ 'template_type': 'default',
+ 'background_color': '#FFFFFF',
+ 'is_default': True,
+ 'sequence': 1
+ })
+ return template
+ except Exception as e:
+ # If there's an error, try to return any template or create a minimal one
+ import logging
+ _logger = logging.getLogger(__name__)
+ _logger.error(f"Error in get_active_template: {str(e)}")
+ # Try to get any template
+ template = self.search([], limit=1)
+ if template:
+ return template
+ # Last resort - return a recordset that won't cause errors
+ return self.browse([])
+
+ @api.model
+ def create(self, vals):
+ """Ensure only one template is active at a time"""
+ # If no active value is set, default to False (don't auto-activate new templates)
+ if 'active' not in vals:
+ vals['active'] = False
+ # Only deactivate others if this one is being set to active
+ if vals.get('active'):
+ # Deactivate all other templates (excluding the one being created)
+ self.search([('active', '=', True)]).write({'active': False})
+ return super(ExpertLoginTemplate, self).create(vals)
+
+ def write(self, vals):
+ """Ensure only one template is active at a time"""
+ # Track if we need to reload
+ need_reload = False
+
+ # Check if we're activating a template
+ if 'active' in vals and vals.get('active'):
+ # Deactivate all other templates (excluding the current recordset)
+ other_templates = self.search([('active', '=', True), ('id', 'not in', self.ids)])
+ if other_templates:
+ # Use super().write to avoid recursion
+ super(ExpertLoginTemplate, other_templates).write({'active': False})
+ need_reload = True
+
+ result = super(ExpertLoginTemplate, self).write(vals)
+
+ # Update template views if active state changed
+ if 'active' in vals or 'template_type' in vals:
+ self._update_template_views()
+
+ # If we're in a list view context and need reload, return reload action
+ if need_reload and self.env.context.get('from_list_view'):
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'reload',
+ }
+
+ return result
+
+ @api.onchange('active')
+ def _onchange_active(self):
+ """When activating a template in the form view, show a warning about deactivating others"""
+ if self.active and not self._origin.active:
+ other_active = self.search([('active', '=', True), ('id', '!=', self.id)])
+ if other_active:
+ return {
+ 'warning': {
+ 'title': 'Other Templates Will Be Deactivated',
+ 'message': f'Activating this template will automatically deactivate: {", ".join(other_active.mapped("name"))}'
+ }
+ }
+
+ def get_template_styles(self):
+ """Get CSS styles for this template"""
+ return {
+ 'background_color': self.background_color or '#FFFFFF',
+ }
+
+ def get_template_name(self):
+ """Get the QWeb template name to render"""
+ template_map = {
+ 'default': 'web.login', # Use standard Odoo login
+ 'modern': 'expert_theme.login_template_modern_page',
+ 'minimal': 'expert_theme.login_template_minimal_page',
+ 'corporate': 'expert_theme.login_template_corporate_page',
+ }
+ return template_map.get(self.template_type, 'web.login')
+
+ def get_signup_template_name(self):
+ """Get the QWeb template name for signup page"""
+ template_map = {
+ 'default': 'auth_signup.signup', # Use standard Odoo signup
+ 'modern': 'expert_theme.signup_template_modern_page',
+ 'minimal': 'expert_theme.signup_template_minimal_page',
+ 'corporate': 'auth_signup.signup', # Use default for now
+ }
+ return template_map.get(self.template_type, 'auth_signup.signup')
+
+ def get_reset_password_template_name(self):
+ """Get the QWeb template name for reset password page"""
+ template_map = {
+ 'default': 'auth_signup.reset_password', # Use standard Odoo reset password
+ 'modern': 'expert_theme.reset_password_template_modern_page',
+ 'minimal': 'expert_theme.reset_password_template_minimal_page',
+ 'corporate': 'auth_signup.reset_password', # Use default for now
+ }
+ return template_map.get(self.template_type, 'auth_signup.reset_password')
+
+ def toggle_active(self):
+ """Toggle active state - ensures only one template is active"""
+ # Ensure we're working with exactly one record
+ self.ensure_one()
+
+ # Get the specific record ID to ensure we activate the correct one
+ template_id = self.id
+
+ # Deactivate all other templates first (including any that might be active)
+ self.env['expert.login.template'].search([
+ ('id', '!=', template_id),
+ ('active', '=', True)
+ ]).write({'active': False})
+
+ # Activate this specific template by ID using browse to ensure we're working with the correct record
+ self.browse(template_id).write({'active': True})
+
+ # Update template view active states
+ self._update_template_views()
+
+ # Return True instead of reload action to avoid potential issues
+ return True
+
+ def _update_template_views(self):
+ """Update the active state of template views based on active template"""
+ try:
+ # Map template types to their view XML IDs
+ template_view_map = {
+ 'modern': 'expert_theme.login_template_modern_page',
+ 'minimal': 'expert_theme.login_template_minimal_page',
+ 'corporate': 'expert_theme.login_template_corporate_page',
+ }
+
+ # Get the active template
+ active_template = self.search([('active', '=', True)], limit=1)
+ if not active_template:
+ # If no active template, deactivate all custom template views
+ for view_xmlid in template_view_map.values():
+ try:
+ view = self.env.ref(view_xmlid, raise_if_not_found=False)
+ if view:
+ view.write({'active': False})
+ except Exception:
+ pass # View doesn't exist, skip
+ return
+
+ # Deactivate all template views first
+ for view_xmlid in template_view_map.values():
+ try:
+ view = self.env.ref(view_xmlid, raise_if_not_found=False)
+ if view:
+ view.write({'active': False})
+ except Exception:
+ pass # View doesn't exist, skip
+
+ # Activate the view for the active template type (if not default)
+ if active_template.template_type != 'default':
+ view_xmlid = template_view_map.get(active_template.template_type)
+ if view_xmlid:
+ try:
+ view = self.env.ref(view_xmlid, raise_if_not_found=False)
+ if view:
+ view.write({'active': True})
+ except Exception:
+ pass # View doesn't exist, skip
+ except Exception:
+ pass # Silently fail if views don't exist
+
+ def action_duplicate(self):
+ """Duplicate the current template"""
+ self.ensure_one()
+ copy_vals = {
+ 'name': self.name + ' (Copy)',
+ 'active': False, # Don't activate the copy
+ 'sequence': self.sequence + 1,
+ 'template_type': self.template_type,
+ 'background_color': self.background_color,
+ 'description': self.description,
+ 'is_default': False,
+ }
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Login Page Template',
+ 'res_model': 'expert.login.template',
+ 'view_mode': 'form',
+ 'target': 'current',
+ 'res_id': self.create(copy_vals).id,
+ 'context': {'default_name': copy_vals['name']},
+ }
+
diff --git a/odex30_base/expert_theme/models/expert_theme_config.py b/odex30_base/expert_theme/models/expert_theme_config.py
new file mode 100644
index 0000000..6d0060d
--- /dev/null
+++ b/odex30_base/expert_theme/models/expert_theme_config.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+
+
+class ExpertThemeConfig(models.Model):
+ _name = 'expert.theme.config'
+ _description = 'Expert Theme Color Configuration'
+ _rec_name = 'name'
+
+ name = fields.Char(string='Configuration Name', required=True, default='Default Theme')
+ active = fields.Boolean(string='Active', default=True)
+
+ # Primary Colors
+ primary_color = fields.Char(string='Primary Color', default='#875A7B', help='Main brand color - affects navbar, primary buttons, and accents')
+ primary_hover = fields.Char(string='Primary Hover', default='#6B4C6B', help='Hover state for primary elements')
+ primary_light = fields.Char(string='Primary Light', default='#A67B9B', help='Light version of primary color')
+
+ # Secondary Colors
+ secondary_color = fields.Char(string='Secondary Color', default='#00A09D', help='Secondary brand color - affects upgrade buttons and secondary elements')
+ secondary_hover = fields.Char(string='Secondary Hover', default='#008B8A', help='Hover state for secondary elements')
+
+ # Button Colors
+ btn_primary = fields.Char(string='Primary Button', default='#875A7B', help='Primary buttons (like "Activate" buttons)')
+ btn_primary_hover = fields.Char(string='Primary Button Hover', default='#6B4C6B', help='Primary button hover state')
+ btn_secondary = fields.Char(string='Secondary Button', default='#00A09D', help='Secondary buttons (like "Upgrade" buttons)')
+ btn_secondary_hover = fields.Char(string='Secondary Button Hover', default='#008B8A', help='Secondary button hover state')
+ btn_light = fields.Char(string='Light Button', default='#F8F9FA', help='Light buttons (like "Learn More" buttons)')
+ btn_light_hover = fields.Char(string='Light Button Hover', default='#E9ECEF', help='Light button hover state')
+ btn_light_text = fields.Char(string='Light Button Text', default='#6C757D', help='Light button text color')
+
+ # Background Colors
+ bg_primary = fields.Char(string='Primary Background', default='#875A7B', help='Navbar background')
+ bg_secondary = fields.Char(string='Secondary Background', default='#F8F9FA', help='Sidebar background')
+ bg_content = fields.Char(string='Content Background', default='#FFFFFF', help='Main content background')
+ bg_gradient_start = fields.Char(string='Gradient Start', default='#f5f7fa', help='Page gradient start color')
+ bg_gradient_end = fields.Char(string='Gradient End', default='#c3cfe2', help='Page gradient end color')
+
+ # Text Colors
+ text_primary = fields.Char(string='Primary Text', default='#212529', help='Main text color (dark)')
+ text_secondary = fields.Char(string='Secondary Text', default='#6C757D', help='Secondary text color (gray)')
+ text_light = fields.Char(string='Light Text', default='#FFFFFF', help='Light text (for dark backgrounds)')
+
+ # Border Colors
+ border_color = fields.Char(string='Border Color', default='#DEE2E6', help='Default border color')
+ border_light = fields.Char(string='Light Border', default='#E9ECEF', help='Light border color')
+
+ # Status Colors
+ success_color = fields.Char(string='Success Color', default='#28A745', help='Success/Active state (green)')
+ warning_color = fields.Char(string='Warning Color', default='#FFC107', help='Warning state (yellow)')
+ danger_color = fields.Char(string='Danger Color', default='#DC3545', help='Danger/Error state (red)')
+ info_color = fields.Char(string='Info Color', default='#17A2B8', help='Info state (blue)')
+
+ @api.model
+ def get_active_config(self):
+ """Get the active configuration or create a default one"""
+ config = self.search([('active', '=', True)], limit=1)
+ if not config:
+ config = self.create({
+ 'name': 'Default Theme',
+ 'active': True
+ })
+ return config
+
+ @api.model
+ def get_css_variables(self):
+ """Get CSS variables for the active configuration"""
+ config = self.get_active_config()
+ return {
+ '--expert-primary-color': config.primary_color,
+ '--expert-primary-hover': config.primary_hover,
+ '--expert-primary-light': config.primary_light,
+ '--expert-secondary-color': config.secondary_color,
+ '--expert-secondary-hover': config.secondary_hover,
+ '--expert-btn-primary': config.btn_primary,
+ '--expert-btn-primary-hover': config.btn_primary_hover,
+ '--expert-btn-secondary': config.btn_secondary,
+ '--expert-btn-secondary-hover': config.btn_secondary_hover,
+ '--expert-btn-light': config.btn_light,
+ '--expert-btn-light-hover': config.btn_light_hover,
+ '--expert-btn-light-text': config.btn_light_text,
+ '--expert-bg-primary': config.bg_primary,
+ '--expert-bg-secondary': config.bg_secondary,
+ '--expert-bg-content': config.bg_content,
+ '--expert-bg-gradient-start': config.bg_gradient_start,
+ '--expert-bg-gradient-end': config.bg_gradient_end,
+ '--expert-text-primary': config.text_primary,
+ '--expert-text-secondary': config.text_secondary,
+ '--expert-text-light': config.text_light,
+ '--expert-border-color': config.border_color,
+ '--expert-border-light': config.border_light,
+ '--expert-success': config.success_color,
+ '--expert-warning': config.warning_color,
+ '--expert-danger': config.danger_color,
+ '--expert-info': config.info_color,
+ }
+
+ def apply_theme(self):
+ """Apply the current theme configuration"""
+ # This method can be called to trigger theme updates
+ return True
diff --git a/odex30_base/expert_theme/security/ir.model.access.csv b/odex30_base/expert_theme/security/ir.model.access.csv
new file mode 100644
index 0000000..f0e95b4
--- /dev/null
+++ b/odex30_base/expert_theme/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_expert_theme_config_user,expert.theme.config.user,model_expert_theme_config,base.group_user,1,1,1,1
+access_expert_theme_config_admin,expert.theme.config.admin,model_expert_theme_config,base.group_system,1,1,1,1
+access_expert_login_template_user,expert.login.template.user,model_expert_login_template,base.group_user,1,1,1,1
+access_expert_login_template_admin,expert.login.template.admin,model_expert_login_template,base.group_system,1,1,1,1
diff --git a/odex30_base/expert_theme/static/description/index.html b/odex30_base/expert_theme/static/description/index.html
new file mode 100644
index 0000000..51a1105
--- /dev/null
+++ b/odex30_base/expert_theme/static/description/index.html
@@ -0,0 +1,65 @@
+
+
+
+
+ Expert Theme
+
+
+
+
+
🎨
+
Expert Theme
+
+
+
Custom Home Page
+
A clean and modern home page that displays only your installed modules for easy navigation.
+
+
+
+
Simple Interface
+
Minimal design focused on functionality - no clutter, just your modules.
+
+
+
+
Responsive Design
+
Works perfectly on desktop, tablet, and mobile devices.
+
+
+
+
Easy Installation
+
Simply install the module and your new home page will be ready to use.