feat(modules): implement module registry for dynamic module management and path matching
This commit is contained in:
+16
-53
@@ -269,40 +269,6 @@ def _get_csrf_token():
|
||||
def _is_csrf_exempt_request():
|
||||
return request.method in {'GET', 'HEAD', 'OPTIONS', 'TRACE'}
|
||||
|
||||
def _is_library_module_path(path):
|
||||
"""Return True when the current request path belongs to the library module."""
|
||||
if not path:
|
||||
return False
|
||||
|
||||
library_prefixes = (
|
||||
'/library',
|
||||
'/library_',
|
||||
'/student_cards',
|
||||
)
|
||||
return path.startswith(library_prefixes)
|
||||
|
||||
def _is_inventory_module_path(path):
|
||||
"""Return True when the current request path belongs to the inventory module."""
|
||||
if not path:
|
||||
return False
|
||||
|
||||
if path == '/' or path.startswith('/home'):
|
||||
return True
|
||||
|
||||
inventory_prefixes = (
|
||||
'/scanner',
|
||||
'/inventory',
|
||||
'/upload_admin',
|
||||
'/manage_filters',
|
||||
'/manage_locations',
|
||||
'/admin_borrowings',
|
||||
'/admin_damaged_items',
|
||||
'/admin/borrowings',
|
||||
'/admin/damaged_items',
|
||||
'/terminplan',
|
||||
)
|
||||
return path.startswith(inventory_prefixes)
|
||||
|
||||
|
||||
@app.before_request
|
||||
def _enforce_csrf_protection():
|
||||
@@ -390,11 +356,14 @@ def _enforce_module_access():
|
||||
if endpoint == 'static' or endpoint.startswith('static') or request.path.startswith('/api/'):
|
||||
return None
|
||||
|
||||
if not cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(request.path):
|
||||
return "Bibliotheks-Modul ist deaktiviert.", 403
|
||||
|
||||
if not cfg.INVENTORY_MODULE_ENABLED and _is_inventory_module_path(request.path):
|
||||
return "Inventar-Modul ist deaktiviert.", 403
|
||||
for name, matcher in cfg.MODULES._path_matchers.items():
|
||||
if matcher(request.path) and not cfg.MODULES.is_enabled(name):
|
||||
msg = {
|
||||
'library': "Bibliotheks-Modul ist deaktiviert.",
|
||||
'inventory': "Inventar-Modul ist deaktiviert.",
|
||||
'student_cards': "Schülerausweis-Modul ist deaktiviert."
|
||||
}.get(name, f"{name.capitalize()}-Modul ist deaktiviert.")
|
||||
return msg, 403
|
||||
|
||||
""" -----------------------------------------------------------After Request Handlers----------------------------------------------------------------------------- """
|
||||
|
||||
@@ -440,24 +409,18 @@ def _csrf_error_response(message='CSRF token fehlt oder ist ungültig.'):
|
||||
|
||||
def _get_current_module(path):
|
||||
"""Resolve the active UI module for navbar separation."""
|
||||
|
||||
if cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(path):
|
||||
session['last_module'] = 'library'
|
||||
return 'library'
|
||||
if cfg.INVENTORY_MODULE_ENABLED and _is_inventory_module_path(path):
|
||||
session['last_module'] = 'inventory'
|
||||
return 'inventory'
|
||||
mod = cfg.MODULES.get_module_for_path(path)
|
||||
if mod:
|
||||
session['last_module'] = mod
|
||||
return mod
|
||||
|
||||
last_module = session.get('last_module')
|
||||
if last_module == 'library' and cfg.LIBRARY_MODULE_ENABLED:
|
||||
return 'library'
|
||||
if last_module == 'inventory' and cfg.INVENTORY_MODULE_ENABLED:
|
||||
return 'inventory'
|
||||
if last_module and cfg.MODULES.is_enabled(last_module):
|
||||
return last_module
|
||||
|
||||
# Default fallback: prefer inventory if enabled, otherwise library, else inventory
|
||||
if cfg.INVENTORY_MODULE_ENABLED:
|
||||
if cfg.MODULES.is_enabled('inventory'):
|
||||
return 'inventory'
|
||||
return 'library' if cfg.LIBRARY_MODULE_ENABLED else 'inventory'
|
||||
return 'library' if cfg.MODULES.is_enabled('library') else 'inventory'
|
||||
|
||||
"""---------------------------------------------User Access Permissions----------------------------------------------------------------------------- """
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from typing import Dict, Any
|
||||
|
||||
class ModuleRegistry:
|
||||
def __init__(self):
|
||||
self._modules: Dict[str, Any] = {}
|
||||
self._path_matchers = {}
|
||||
|
||||
def register(self, name: str, settings_bool, path_matcher=lambda path: False):
|
||||
self._modules[name] = settings_bool
|
||||
self._path_matchers[name] = path_matcher
|
||||
|
||||
def is_enabled(self, name: str) -> bool:
|
||||
if name not in self._modules:
|
||||
return False
|
||||
return bool(self._modules[name])
|
||||
|
||||
def get_all_status(self) -> Dict[str, bool]:
|
||||
return {name: bool(sys_bool) for name, sys_bool in self._modules.items()}
|
||||
|
||||
def get_module_for_path(self, path: str) -> str:
|
||||
for name, matcher in self._path_matchers.items():
|
||||
if self.is_enabled(name) and matcher(path):
|
||||
return name
|
||||
return None
|
||||
|
||||
registry = ModuleRegistry()
|
||||
+21
-1
@@ -205,11 +205,31 @@ class _TenantAwareBool:
|
||||
return f"_TenantAwareBool(module_name={self.module_name!r}, value={self.resolve()!r})"
|
||||
|
||||
|
||||
from module_registry import registry as MODULES
|
||||
|
||||
INVENTORY_MODULE_ENABLED = _TenantAwareBool('inventory', _get(_conf, ['modules', 'inventory', 'enabled'], DEFAULTS['modules']['inventory']['enabled']))
|
||||
LIBRARY_MODULE_ENABLED = _TenantAwareBool('library', _get(_conf, ['modules', 'library', 'enabled'], DEFAULTS['modules']['library']['enabled']))
|
||||
STUDENT_CARDS_MODULE_ENABLED = _TenantAwareBool('student_cards', _get(_conf, ['modules', 'student_cards', 'enabled'], DEFAULTS['modules']['student_cards']['enabled']))
|
||||
|
||||
def _match_inventory(path):
|
||||
if not path: return False
|
||||
if path == '/' or path.startswith('/home'): return True
|
||||
return path.startswith(('/scanner', '/inventory', '/upload_admin', '/manage_filters', '/manage_locations', '/admin_borrowings', '/admin_damaged_items', '/admin/borrowings', '/admin/damaged_items', '/terminplan'))
|
||||
|
||||
def _match_library(path):
|
||||
if not path: return False
|
||||
return path.startswith(('/library', '/library_', '/student_cards'))
|
||||
|
||||
def _match_student_cards(path):
|
||||
if not path: return False
|
||||
return path.startswith(('/student_cards'))
|
||||
|
||||
# Register core modules into the pipeline
|
||||
MODULES.register('inventory', INVENTORY_MODULE_ENABLED, _match_inventory)
|
||||
MODULES.register('library', LIBRARY_MODULE_ENABLED, _match_library)
|
||||
MODULES.register('student_cards', STUDENT_CARDS_MODULE_ENABLED, _match_student_cards)
|
||||
|
||||
STUDENT_DEFAULT_BORROW_DAYS = int(_get(_conf, ['modules', 'student_cards', 'default_borrow_days'], DEFAULTS['modules']['student_cards']['default_borrow_days']))
|
||||
STUDENT_MAX_BORROW_DAYS = int(_get(_conf, ['modules', 'student_cards', 'max_borrow_days'], DEFAULTS['modules']['student_cards']['max_borrow_days']))
|
||||
|
||||
# Upload/paths
|
||||
ALLOWED_EXTENSIONS = set(_get(_conf, ['allowed_extensions'], DEFAULTS['upload']['allowed_extensions']))
|
||||
|
||||
Reference in New Issue
Block a user