Files
TimeTracker/app/utils/module_helpers.py
Dries Peeters 083dd9f1f2 feat: Implement comprehensive module system with visibility controls
- Add centralized module registry system (ModuleRegistry) for managing
  module metadata, dependencies, and visibility across the application
- Create module helper utilities with decorators (@module_enabled) and
  helper functions for route protection and template access
- Add database migration (092) to add missing module visibility flags
  to settings and users tables for granular control
- Extend Settings and User models with additional module visibility
  flags for CRM, Finance, Tools, and Advanced features
- Implement admin module management UI for system-wide module
  enable/disable controls
- Add module checks to routes (calendar, contacts, deals, expenses,
  invoices, leads, custom_reports) to enforce visibility rules
- Update scheduled report service and report templates to respect
  module visibility settings
- Bump version to 4.8.0 in setup.py
- Add comprehensive documentation for module integration planning
  and implementation analysis
2025-12-29 14:13:32 +01:00

131 lines
4.1 KiB
Python

"""
Module Helper Utilities
Provides decorators and helper functions for checking module availability
and protecting routes based on module flags.
"""
from functools import wraps
from flask import abort, redirect, url_for, flash, current_app
from flask_login import current_user
from flask_babel import gettext as _
from app.models import Settings
from app.utils.module_registry import ModuleRegistry
def module_enabled(module_id: str, redirect_to: str = None):
"""
Decorator to require a module to be enabled for a route.
Args:
module_id: The module ID to check
redirect_to: Optional route name to redirect to if module is disabled
Usage:
@module_enabled("calendar")
def view_calendar():
return render_template("calendar/view.html")
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated:
if redirect_to:
return redirect(url_for(redirect_to))
abort(403)
settings = Settings.get_settings()
if not ModuleRegistry.is_enabled(module_id, settings, current_user):
if current_user.is_admin:
module = ModuleRegistry.get(module_id)
module_name = module.name if module else module_id
flash(
_("Module '%(module)s' is disabled. Enable it in Settings.", module=module_name),
"warning"
)
if redirect_to:
return redirect(url_for(redirect_to))
return redirect(url_for('admin.settings'))
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
def is_module_enabled(module_id: str) -> bool:
"""
Check if a module is enabled for the current user.
Args:
module_id: The module ID to check
Returns:
True if module is enabled, False otherwise
"""
if not current_user.is_authenticated:
return False
try:
settings = Settings.get_settings()
return ModuleRegistry.is_enabled(module_id, settings, current_user)
except Exception:
# If we can't check, default to False for safety
return False
def get_enabled_modules(category=None):
"""
Get all enabled modules, optionally filtered by category.
Args:
category: Optional ModuleCategory to filter by
Returns:
List of enabled ModuleDefinition objects
"""
if not current_user.is_authenticated:
return []
try:
settings = Settings.get_settings()
modules = ModuleRegistry.get_enabled_modules(settings, current_user)
if category:
from app.utils.module_registry import ModuleCategory
if isinstance(category, str):
try:
category = ModuleCategory(category)
except ValueError:
return []
modules = [m for m in modules if m.category == category]
return modules
except Exception:
return []
def init_module_helpers(app):
"""
Initialize module helper functions for use in templates and routes.
This should be called during app initialization.
"""
# Initialize module registry
ModuleRegistry.initialize_defaults()
@app.context_processor
def inject_module_helpers():
"""Make module helpers available in templates"""
from app.utils.module_registry import ModuleCategory
return {
"is_module_enabled": is_module_enabled,
"get_enabled_modules": get_enabled_modules,
"get_modules_by_category": lambda cat: ModuleRegistry.get_by_category(cat),
"ModuleCategory": ModuleCategory,
}
# Also make it available as a global function
app.jinja_env.globals['is_module_enabled'] = is_module_enabled
app.jinja_env.globals['get_enabled_modules'] = get_enabled_modules