Compare commits

..

8 Commits

7 changed files with 180 additions and 177 deletions
View File
+115 -169
View File
@@ -269,40 +269,6 @@ def _get_csrf_token():
def _is_csrf_exempt_request(): def _is_csrf_exempt_request():
return request.method in {'GET', 'HEAD', 'OPTIONS', 'TRACE'} 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 @app.before_request
def _enforce_csrf_protection(): def _enforce_csrf_protection():
@@ -390,11 +356,14 @@ def _enforce_module_access():
if endpoint == 'static' or endpoint.startswith('static') or request.path.startswith('/api/'): if endpoint == 'static' or endpoint.startswith('static') or request.path.startswith('/api/'):
return None return None
if not cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(request.path): for name, matcher in cfg.MODULES._path_matchers.items():
return "Bibliotheks-Modul ist deaktiviert.", 403 if matcher(request.path) and not cfg.MODULES.is_enabled(name):
msg = {
if not cfg.INVENTORY_MODULE_ENABLED and _is_inventory_module_path(request.path): 'library': "Bibliotheks-Modul ist deaktiviert.",
return "Inventar-Modul ist deaktiviert.", 403 '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----------------------------------------------------------------------------- """ """ -----------------------------------------------------------After Request Handlers----------------------------------------------------------------------------- """
@@ -440,24 +409,18 @@ def _csrf_error_response(message='CSRF token fehlt oder ist ungültig.'):
def _get_current_module(path): def _get_current_module(path):
"""Resolve the active UI module for navbar separation.""" """Resolve the active UI module for navbar separation."""
mod = cfg.MODULES.get_module_for_path(path)
if cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(path): if mod:
session['last_module'] = 'library' session['last_module'] = mod
return 'library' return mod
if cfg.INVENTORY_MODULE_ENABLED and _is_inventory_module_path(path):
session['last_module'] = 'inventory'
return 'inventory'
last_module = session.get('last_module') last_module = session.get('last_module')
if last_module == 'library' and cfg.LIBRARY_MODULE_ENABLED: if last_module and cfg.MODULES.is_enabled(last_module):
return 'library' return last_module
if last_module == 'inventory' and cfg.INVENTORY_MODULE_ENABLED:
return 'inventory'
# Default fallback: prefer inventory if enabled, otherwise library, else inventory if cfg.MODULES.is_enabled('inventory'):
if cfg.INVENTORY_MODULE_ENABLED:
return '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----------------------------------------------------------------------------- """ """---------------------------------------------User Access Permissions----------------------------------------------------------------------------- """
@@ -1063,9 +1026,9 @@ def inject_version():
'csrf_token': csrf_token, 'csrf_token': csrf_token,
'CURRENT_MODULE': current_module, 'CURRENT_MODULE': current_module,
'school_periods': SCHOOL_PERIODS, 'school_periods': SCHOOL_PERIODS,
'inventory_module_enabled': cfg.INVENTORY_MODULE_ENABLED, 'inventory_module_enabled': cfg.MODULES.is_enabled('inventory'),
'library_module_enabled': cfg.LIBRARY_MODULE_ENABLED, 'library_module_enabled': cfg.MODULES.is_enabled('library'),
'student_cards_module_enabled': cfg.STUDENT_CARDS_MODULE_ENABLED, 'student_cards_module_enabled': cfg.MODULES.is_enabled('student_cards'),
'is_admin': is_admin, 'is_admin': is_admin,
'unread_notification_count': unread_notification_count, 'unread_notification_count': unread_notification_count,
'current_permissions': current_permissions, 'current_permissions': current_permissions,
@@ -1641,6 +1604,20 @@ def _load_tabular_upload(uploaded_file):
workbook.close() workbook.close()
return header_row, data_rows return header_row, data_rows
def _deny_if_unauthenticated_file_access():
"""Block file-serving routes unless a user is logged in."""
if 'username' not in session:
return Response('Forbidden', status=403)
return None
def _student_card_id_slug(value):
"""Build a compact identifier fragment from a name or class value."""
normalized = _normalize_excel_header(value)
if not normalized:
return ''
return re.sub(r'[^a-z0-9]+', '', normalized).upper()
"""-------------------------------------------------------------Filter----------------------------------------------------------------------------- """ """-------------------------------------------------------------Filter----------------------------------------------------------------------------- """
FILTER_SELECT_ALL_TOKEN = '__ALL__' FILTER_SELECT_ALL_TOKEN = '__ALL__'
@@ -1704,33 +1681,7 @@ def _is_public_host(hostname):
return public_seen return public_seen
"""-------------------------------------------------------------Student Cards Excel Import----------------------------------------------------------------------------- """
def _deny_if_unauthenticated_file_access():
"""Block file-serving routes unless a user is logged in."""
if 'username' not in session:
return Response('Forbidden', status=403)
return None
def _student_card_id_slug(value):
"""Build a compact identifier fragment from a name or class value."""
normalized = _normalize_excel_header(value)
if not normalized:
return ''
return re.sub(r'[^a-z0-9]+', '', normalized).upper()
def _name_to_alias(full_name):
"""Convert clear names to deterministic aliases, e.g. Simon Frings -> SimFri."""
text = sanitize_form_value(full_name)
if not text:
return 'User'
parts = [p for p in re.split(r'\s+', text) if p]
if len(parts) >= 2:
return us.build_name_synonym(parts[0], parts[-1])
return us.build_name_synonym(parts[0], '')
def _build_student_card_excel_id(student_name, class_name, row_number, used_ids): def _build_student_card_excel_id(student_name, class_name, row_number, used_ids):
"""Create a stable student-card ID without embedding personal names.""" """Create a stable student-card ID without embedding personal names."""
@@ -1762,7 +1713,7 @@ def _upload_student_cards_excel():
flash('Administratorrechte erforderlich.', 'error') flash('Administratorrechte erforderlich.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
if not cfg.STUDENT_CARDS_MODULE_ENABLED: if not cfg.MODULES.is_enabled('student_cards'):
flash('Schülerausweis-Modul ist deaktiviert.', 'error') flash('Schülerausweis-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -1858,8 +1809,6 @@ def _upload_student_cards_excel():
student_name = f'{first_name} {last_name}'.strip() student_name = f'{first_name} {last_name}'.strip()
validation_warnings.append((row_number, 'Schülername wurde aus Vorname und Nachname zusammengesetzt')) validation_warnings.append((row_number, 'Schülername wurde aus Vorname und Nachname zusammengesetzt'))
student_name_alias = _name_to_alias(student_name)
if not ausweis_id and not student_name and not class_name: if not ausweis_id and not student_name and not class_name:
continue continue
@@ -1884,7 +1833,7 @@ def _upload_student_cards_excel():
planned_rows.append({ planned_rows.append({
'row_number': row_number, 'row_number': row_number,
'ausweis_id': ausweis_id, 'ausweis_id': ausweis_id,
'student_name': student_name_alias, 'student_name': student_name,
'class_name': class_name, 'class_name': class_name,
'notes': notes, 'notes': notes,
'default_borrow_days': default_borrow_days, 'default_borrow_days': default_borrow_days,
@@ -1959,7 +1908,7 @@ def _upload_excel_items(scope='inventory'):
fallback_route = 'library_admin' if is_library_scope else 'upload_admin' fallback_route = 'library_admin' if is_library_scope else 'upload_admin'
if is_library_scope: if is_library_scope:
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error') flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home')) return redirect(url_for('home'))
@@ -2290,6 +2239,7 @@ def upload_student_cards_excel():
"""Bulk import student cards from Excel.""" """Bulk import student cards from Excel."""
return _upload_student_cards_excel() return _upload_student_cards_excel()
"""-------------------------------------------------------------File Serving-----------------------------------------------------------------------------"""
@app.route('/uploads/<filename>') @app.route('/uploads/<filename>')
def uploaded_file(filename): def uploaded_file(filename):
@@ -2581,27 +2531,11 @@ def catch_all_files(filename):
os.path.join(BASE_DIR, 'static') os.path.join(BASE_DIR, 'static')
] ]
# Check development paths first
for directory in possible_dirs: for directory in possible_dirs:
file_path = os.path.join(directory, filename) file_path = os.path.join(directory, filename)
if os.path.isfile(file_path): if os.path.isfile(file_path):
return send_from_directory(directory, os.path.basename(filename)) return send_from_directory(directory, os.path.basename(filename))
# Check production paths if available
if os.path.exists("/var/Inventarsystem/Web"):
prod_dirs = [
"/var/Inventarsystem/Web/uploads",
"/var/Inventarsystem/Web/thumbnails",
"/var/Inventarsystem/Web/previews",
# "/var/Inventarsystem/Web/QRCodes", # QR Code serving deactivated
"/var/Inventarsystem/Web/static"
]
for directory in prod_dirs:
file_path = os.path.join(directory, filename)
if os.path.isfile(file_path):
return send_from_directory(directory, os.path.basename(filename))
# Check if this looks like an image request # Check if this looks like an image request
if any(filename.lower().endswith(ext) for ext in ['png', 'jpg', 'jpeg', 'gif', 'svg']): if any(filename.lower().endswith(ext) for ext in ['png', 'jpg', 'jpeg', 'gif', 'svg']):
# Use a placeholder image if file not found - first try SVG, then PNG # Use a placeholder image if file not found - first try SVG, then PNG
@@ -2621,6 +2555,7 @@ def catch_all_files(filename):
print(f"Error in catch-all route for {filename}: {str(e)}") print(f"Error in catch-all route for {filename}: {str(e)}")
return Response(f"Error serving file: {str(e)}", status=500) return Response(f"Error serving file: {str(e)}", status=500)
"""-------------------------------------------------------------Main Views-----------------------------------------------------------------------------"""
@app.route('/test_connection', methods=['GET']) @app.route('/test_connection', methods=['GET'])
def test_connection(): def test_connection():
@@ -2671,8 +2606,8 @@ def home():
flash('Bitte mit registriertem Konto anmelden!', 'error') flash('Bitte mit registriertem Konto anmelden!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.INVENTORY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('inventory'):
if cfg.LIBRARY_MODULE_ENABLED: if cfg.MODULES.is_enabled('library'):
return redirect(url_for('library_view')) return redirect(url_for('library_view'))
else: else:
return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403 return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403
@@ -2681,8 +2616,8 @@ def home():
return render_template( return render_template(
'main.html', 'main.html',
username=session['username'], username=session['username'],
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS, student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
) )
@@ -2710,8 +2645,8 @@ def home_admin():
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.INVENTORY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('inventory'):
if cfg.LIBRARY_MODULE_ENABLED: if cfg.MODULES.is_enabled('library'):
return redirect(url_for('library_admin')) return redirect(url_for('library_admin'))
else: else:
return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403 return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403
@@ -2722,8 +2657,8 @@ def home_admin():
return render_template( return render_template(
'main_admin.html', 'main_admin.html',
username=session['username'], username=session['username'],
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS, student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS, student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS,
school_info=_get_school_info_for_export(), school_info=_get_school_info_for_export(),
@@ -2741,8 +2676,8 @@ def tutorial_page():
'tutorial.html', 'tutorial.html',
username=session['username'], username=session['username'],
is_admin=us.check_admin(session['username']), is_admin=us.check_admin(session['username']),
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS, student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
) )
@@ -2760,16 +2695,16 @@ def library_view():
if 'username' not in session: if 'username' not in session:
flash('Bitte mit registriertem Konto anmelden!', 'error') flash('Bitte mit registriertem Konto anmelden!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error') flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home')) return redirect(url_for('home'))
return render_template( return render_template(
'library_table.html', 'library_table.html',
username=session['username'], username=session['username'],
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
is_admin=us.check_admin(session['username']), is_admin=us.check_admin(session['username']),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS, student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
) )
@@ -2784,7 +2719,7 @@ def library_loans_admin():
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error') flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -2885,8 +2820,8 @@ def library_loans_admin():
'library_borrowings_admin.html', 'library_borrowings_admin.html',
loan_entries=loan_entries, loan_entries=loan_entries,
damaged_items=damaged_items, damaged_items=damaged_items,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
) )
except Exception as e: except Exception as e:
app.logger.error(f"Error loading library loans admin view: {e}") app.logger.error(f"Error loading library loans admin view: {e}")
@@ -3021,9 +2956,9 @@ def api_library_scan_action():
""" """
if 'username' not in session: if 'username' not in session:
return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401 return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403 return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403
if not cfg.STUDENT_CARDS_MODULE_ENABLED: if not cfg.MODULES.is_enabled('student_cards'):
return jsonify({'ok': False, 'message': 'Schülerausweis-Modul ist deaktiviert.'}), 403 return jsonify({'ok': False, 'message': 'Schülerausweis-Modul ist deaktiviert.'}), 403
payload = request.get_json(silent=True) or {} payload = request.get_json(silent=True) or {}
@@ -3227,7 +3162,7 @@ def api_library_item_update(item_id):
return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401 return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
return jsonify({'ok': False, 'message': 'Administratorrechte erforderlich.'}), 403 return jsonify({'ok': False, 'message': 'Administratorrechte erforderlich.'}), 403
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403 return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403
payload = request.get_json(silent=True) or {} payload = request.get_json(silent=True) or {}
@@ -3358,8 +3293,8 @@ def upload_admin():
'upload_admin.html', 'upload_admin.html',
username=session['username'], username=session['username'],
duplicate_data=duplicate_data, duplicate_data=duplicate_data,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
show_library_features=False, show_library_features=False,
upload_mode='item', upload_mode='item',
page_title='Artikel hochladen', page_title='Artikel hochladen',
@@ -3380,7 +3315,7 @@ def library_admin():
if not _action_access_allowed(permissions, 'can_insert'): if not _action_access_allowed(permissions, 'can_insert'):
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error') flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -3424,8 +3359,8 @@ def library_admin():
'upload_admin.html', 'upload_admin.html',
username=session['username'], username=session['username'],
duplicate_data=duplicate_data, duplicate_data=duplicate_data,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
show_library_features=True, show_library_features=True,
upload_mode='library', upload_mode='library',
page_title='Bücher hochladen', page_title='Bücher hochladen',
@@ -3445,7 +3380,7 @@ def student_cards_admin():
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.STUDENT_CARDS_MODULE_ENABLED: if not cfg.MODULES.is_enabled('student_cards'):
flash('Schülerausweis-Modul ist deaktiviert.', 'error') flash('Schülerausweis-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -3481,7 +3416,7 @@ def student_cards_admin():
action = request.form.get('action', 'add') action = request.form.get('action', 'add')
ausweis_id = request.form.get('ausweis_id', '').strip().upper() ausweis_id = request.form.get('ausweis_id', '').strip().upper()
student_name = request.form.get('student_name', '').strip() student_name = request.form.get('student_name', '').strip()
student_name_alias = _name_to_alias(student_name) student_name_alias = student_name
default_borrow_days = request.form.get('default_borrow_days', 14) default_borrow_days = request.form.get('default_borrow_days', 14)
class_name = request.form.get('class_name', '').strip() class_name = request.form.get('class_name', '').strip()
notes = request.form.get('notes', '').strip() notes = request.form.get('notes', '').strip()
@@ -3571,8 +3506,8 @@ def student_cards_admin():
edit_mode=edit_mode, edit_mode=edit_mode,
form_data=form_data, form_data=form_data,
config={'default': cfg.STUDENT_DEFAULT_BORROW_DAYS}, config={'default': cfg.STUDENT_DEFAULT_BORROW_DAYS},
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
) )
@@ -3588,7 +3523,7 @@ def student_cards_print():
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.STUDENT_CARDS_MODULE_ENABLED: if not cfg.MODULES.is_enabled('student_cards'):
flash('Schülerausweis-Modul ist deaktiviert.', 'error') flash('Schülerausweis-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -3620,7 +3555,7 @@ def student_card_barcode_print():
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.STUDENT_CARDS_MODULE_ENABLED: if not cfg.MODULES.is_enabled('student_cards'):
flash('Schülerausweis-Modul ist deaktiviert.', 'error') flash('Schülerausweis-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -3651,7 +3586,7 @@ def student_card_barcode_download():
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
flash('Zugriff verweigert.', 'error') flash('Zugriff verweigert.', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.STUDENT_CARDS_MODULE_ENABLED: if not cfg.MODULES.is_enabled('student_cards'):
flash('Schülerausweis-Modul ist deaktiviert.', 'error') flash('Schülerausweis-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -3825,7 +3760,7 @@ def student_card_single_barcode_download(card_id):
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
flash('Zugriff verweigert.', 'error') flash('Zugriff verweigert.', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.STUDENT_CARDS_MODULE_ENABLED: if not cfg.MODULES.is_enabled('student_cards'):
flash('Schülerausweis-Modul ist deaktiviert.', 'error') flash('Schülerausweis-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -4694,7 +4629,7 @@ def upload_item():
item_isbn = '' item_isbn = ''
item_type = 'general' item_type = 'general'
if cfg.LIBRARY_MODULE_ENABLED: if cfg.MODULES.is_enabled('library'):
item_isbn = normalize_and_validate_isbn(isbn_raw) item_isbn = normalize_and_validate_isbn(isbn_raw)
if isbn_raw and not item_isbn: if isbn_raw and not item_isbn:
error_msg = 'Ungültige ISBN. Bitte ISBN-10 oder ISBN-13 verwenden.' error_msg = 'Ungültige ISBN. Bitte ISBN-10 oder ISBN-13 verwenden.'
@@ -4706,7 +4641,7 @@ def upload_item():
item_type = 'book' item_type = 'book'
if upload_mode == 'library': if upload_mode == 'library':
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
error_msg = 'Bibliotheks-Modul ist deaktiviert.' error_msg = 'Bibliotheks-Modul ist deaktiviert.'
if is_mobile: if is_mobile:
return jsonify({'success': False, 'message': error_msg}), 400 return jsonify({'success': False, 'message': error_msg}), 400
@@ -4736,6 +4671,10 @@ def upload_item():
if is_mobile: if is_mobile:
return jsonify({'success': False, 'message': error_msg}), 400 return jsonify({'success': False, 'message': error_msg}), 400
else: else:
conflicting_items = it.get_item_by_code_4(code_4[0])
if conflicting_items:
flash('Der Code wird bereits verwendet. Das existierende Element wurde geöffnet.', 'info')
return redirect(url_for(success_redirect_endpoint, open_item_modal=str(conflicting_items[0]['_id'])))
flash(error_msg, 'error') flash(error_msg, 'error')
return redirect(url_for(success_redirect_endpoint)) return redirect(url_for(success_redirect_endpoint))
@@ -4760,6 +4699,10 @@ def upload_item():
error_msg = f'Der Einzelcode "{specific_code}" wird bereits verwendet.' error_msg = f'Der Einzelcode "{specific_code}" wird bereits verwendet.'
if is_mobile: if is_mobile:
return jsonify({'success': False, 'message': error_msg}), 400 return jsonify({'success': False, 'message': error_msg}), 400
conflicting_items = it.get_item_by_code_4(specific_code)
if conflicting_items:
flash(f'Der Einzelcode "{specific_code}" wird bereits verwendet. Das existierende Element wurde geöffnet.', 'info')
return redirect(url_for(success_redirect_endpoint, open_item_modal=str(conflicting_items[0]['_id'])))
flash(error_msg, 'error') flash(error_msg, 'error')
return redirect(url_for(success_redirect_endpoint)) return redirect(url_for(success_redirect_endpoint))
@@ -5881,7 +5824,7 @@ def edit_item(id):
item_isbn = '' item_isbn = ''
item_type = 'general' item_type = 'general'
if cfg.LIBRARY_MODULE_ENABLED: if cfg.MODULES.is_enabled('library'):
item_isbn = normalize_and_validate_isbn(isbn_raw) item_isbn = normalize_and_validate_isbn(isbn_raw)
if isbn_raw and not item_isbn: if isbn_raw and not item_isbn:
flash('Ungültige ISBN. Bitte ISBN-10 oder ISBN-13 verwenden.', 'error') flash('Ungültige ISBN. Bitte ISBN-10 oder ISBN-13 verwenden.', 'error')
@@ -5891,6 +5834,10 @@ def edit_item(id):
# Check if code is unique (excluding the current item) # Check if code is unique (excluding the current item)
if code_4 and not it.is_code_unique(code_4, exclude_id=id): if code_4 and not it.is_code_unique(code_4, exclude_id=id):
conflicting_items = it.get_item_by_code_4(code_4)
if conflicting_items:
flash('Der Code wird bereits verwendet. Das existierende Element wurde stattdessen geöffnet.', 'info')
return redirect(url_for('home_admin', open_item_modal=str(conflicting_items[0]['_id'])))
flash('Der Code wird bereits verwendet. Bitte wählen Sie einen anderen Code.', 'error') flash('Der Code wird bereits verwendet. Bitte wählen Sie einen anderen Code.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -6164,7 +6111,7 @@ def ausleihen(id):
username = session['username'] username = session['username']
requested_return_to = (request.form.get('return_to') or '').strip().lower() requested_return_to = (request.form.get('return_to') or '').strip().lower()
if requested_return_to == 'library' and cfg.LIBRARY_MODULE_ENABLED: if requested_return_to == 'library' and cfg.MODULES.is_enabled('library'):
redirect_target = 'library_view' redirect_target = 'library_view'
else: else:
redirect_target = 'home_admin' if us.check_admin(username) else 'home' redirect_target = 'home_admin' if us.check_admin(username) else 'home'
@@ -6182,7 +6129,7 @@ def ausleihen(id):
# Library media can only be borrowed with a valid student card. # Library media can only be borrowed with a valid student card.
if is_library_item: if is_library_item:
if not cfg.STUDENT_CARDS_MODULE_ENABLED: if not cfg.MODULES.is_enabled('student_cards'):
flash('Bibliotheksmedien können nur mit aktivem Schülerausweis-Modul ausgeliehen werden.', 'error') flash('Bibliotheksmedien können nur mit aktivem Schülerausweis-Modul ausgeliehen werden.', 'error')
return redirect(url_for(redirect_target)) return redirect(url_for(redirect_target))
if not student_card_id: if not student_card_id:
@@ -6211,7 +6158,7 @@ def ausleihen(id):
if client: if client:
client.close() client.close()
if cfg.STUDENT_CARDS_MODULE_ENABLED: if cfg.MODULES.is_enabled('student_cards'):
duration_raw = (request.form.get('borrow_duration_days') or '').strip() duration_raw = (request.form.get('borrow_duration_days') or '').strip()
if duration_raw: if duration_raw:
try: try:
@@ -7131,8 +7078,8 @@ def register():
return redirect(url_for('home')) return redirect(url_for('home'))
return render_template( return render_template(
'register.html', 'register.html',
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS, student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
) )
@@ -7198,8 +7145,8 @@ def user_del():
return render_template( return render_template(
'user_del.html', 'user_del.html',
users=users_list, users=users_list,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
) )
@@ -7339,8 +7286,8 @@ def admin_borrowings():
return render_template( return render_template(
'admin_borrowings.html', 'admin_borrowings.html',
entries=entries, entries=entries,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
) )
"""-----------------------------------------------------------Audit Routes-------------------------------------------------------""" """-----------------------------------------------------------Audit Routes-------------------------------------------------------"""
@@ -7401,8 +7348,8 @@ def admin_audit_dashboard():
'admin_audit.html', 'admin_audit.html',
verify_result=verify_result, verify_result=verify_result,
audit_rows=audit_rows, audit_rows=audit_rows,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
) )
except Exception as exc: except Exception as exc:
app.logger.error(f"Error loading audit dashboard: {exc}") app.logger.error(f"Error loading audit dashboard: {exc}")
@@ -8148,7 +8095,7 @@ def library_item_invoices(item_id):
if 'username' not in session or not us.check_admin(session['username']): if 'username' not in session or not us.check_admin(session['username']):
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error') flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@@ -8216,8 +8163,8 @@ def library_item_invoices(item_id):
'isbn': item_doc.get('ISBN', ''), 'isbn': item_doc.get('ISBN', ''),
}, },
invoices=entries, invoices=entries,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
) )
except Exception as e: except Exception as e:
app.logger.error(f"Error loading invoice history for item {item_id}: {e}") app.logger.error(f"Error loading invoice history for item {item_id}: {e}")
@@ -8365,13 +8312,12 @@ def admin_anonymize_names():
for card_doc in student_cards_col.find({}, {'SchülerName': 1, 'Klasse': 1, 'Notizen': 1}): for card_doc in student_cards_col.find({}, {'SchülerName': 1, 'Klasse': 1, 'Notizen': 1}):
decrypted = _decrypt_student_card_doc(card_doc) decrypted = _decrypt_student_card_doc(card_doc)
alias = _name_to_alias(decrypted.get('SchülerName', ''))
class_name = sanitize_form_value(decrypted.get('Klasse', '')) class_name = sanitize_form_value(decrypted.get('Klasse', ''))
notes = sanitize_form_value(decrypted.get('Notizen', '')) notes = sanitize_form_value(decrypted.get('Notizen', ''))
encrypted_payload = encrypt_document_fields( encrypted_payload = encrypt_document_fields(
{ {
'SchülerName': alias, 'SchülerName': decrypted.get('SchülerName', ''),
'Klasse': class_name, 'Klasse': class_name,
'Notizen': notes, 'Notizen': notes,
}, },
@@ -8544,8 +8490,8 @@ def logs():
return render_template( return render_template(
'logs.html', 'logs.html',
items=formatted_items, items=formatted_items,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
) )
@@ -8610,8 +8556,8 @@ def manage_filters():
'manage_filters.html', 'manage_filters.html',
filter1_values=filter1_values, filter1_values=filter1_values,
filter2_values=filter2_values, filter2_values=filter2_values,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
) )
@app.route('/add_filter_value/<int:filter_num>', methods=['POST']) @app.route('/add_filter_value/<int:filter_num>', methods=['POST'])
@@ -8742,7 +8688,7 @@ def fetch_book_info(isbn):
if 'username' not in session or not us.check_admin(session['username']): if 'username' not in session or not us.check_admin(session['username']):
return jsonify({"error": "Not authorized"}), 403 return jsonify({"error": "Not authorized"}), 403
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
return jsonify({"error": "Bibliotheks-Modul ist deaktiviert."}), 403 return jsonify({"error": "Bibliotheks-Modul ist deaktiviert."}), 403
try: try:
@@ -8837,7 +8783,7 @@ def download_book_cover():
return jsonify({"error": "Not authorized"}), 403 return jsonify({"error": "Not authorized"}), 403
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
return jsonify({"error": "Admin privileges required"}), 403 return jsonify({"error": "Admin privileges required"}), 403
if not cfg.LIBRARY_MODULE_ENABLED: if not cfg.MODULES.is_enabled('library'):
return jsonify({"error": "Bibliotheks-Modul ist deaktiviert."}), 403 return jsonify({"error": "Bibliotheks-Modul ist deaktiviert."}), 403
try: try:
@@ -9204,8 +9150,8 @@ def notifications_view():
user_notifications=user_notifications, user_notifications=user_notifications,
admin_notifications=admin_notifications, admin_notifications=admin_notifications,
is_admin_user=is_admin_user, is_admin_user=is_admin_user,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
) )
except Exception as exc: except Exception as exc:
app.logger.error(f"Error loading notifications: {exc}") app.logger.error(f"Error loading notifications: {exc}")
@@ -9436,8 +9382,8 @@ def admin_damaged_items():
return render_template( return render_template(
'admin_damaged_items.html', 'admin_damaged_items.html',
damaged_items=damaged_rows, damaged_items=damaged_rows,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
) )
except Exception as exc: except Exception as exc:
app.logger.error(f"Error loading damaged-items admin view: {exc}") app.logger.error(f"Error loading damaged-items admin view: {exc}")
@@ -9540,8 +9486,8 @@ def manage_locations():
return render_template( return render_template(
'manage_locations.html', 'manage_locations.html',
location_values=location_values, location_values=location_values,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
) )
@@ -9649,8 +9595,8 @@ def admin_school_settings():
school_info=current_school, school_info=current_school,
tenant_id=tenant_id, tenant_id=tenant_id,
tenant_db=tenant_db, tenant_db=tenant_db,
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED, library_module_enabled=cfg.MODULES.is_enabled('library'),
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED, student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
) )
@app.route('/check_code_unique/<code>') @app.route('/check_code_unique/<code>')
+26
View File
@@ -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()
+22 -1
View File
@@ -205,11 +205,32 @@ class _TenantAwareBool:
return f"_TenantAwareBool(module_name={self.module_name!r}, value={self.resolve()!r})" 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'])) 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'])) 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'])) 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_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'])) STUDENT_MAX_BORROW_DAYS = int(_get(_conf, ["modules", "student_cards", "max_borrow_days"], DEFAULTS["modules"]["student_cards"]["max_borrow_days"]))
# Upload/paths # Upload/paths
ALLOWED_EXTENSIONS = set(_get(_conf, ['allowed_extensions'], DEFAULTS['upload']['allowed_extensions'])) ALLOWED_EXTENSIONS = set(_get(_conf, ['allowed_extensions'], DEFAULTS['upload']['allowed_extensions']))
+3 -3
View File
@@ -459,7 +459,7 @@
<div id="schedule-modal" class="item-modal"> <div id="schedule-modal" class="item-modal">
<div class="modal-content"> <div class="modal-content">
<span class="close-modal" onclick="closeScheduleModal()">&times;</span> <span class="close-modal" onclick="closeScheduleModal()">&times;</span>
<h2>Termin planen</h2> <h2>Reservieren</h2>
<form id="schedule-form"> <form id="schedule-form">
<input type="hidden" id="schedule-item-id" name="item_id"> <input type="hidden" id="schedule-item-id" name="item_id">
@@ -1057,7 +1057,7 @@
: :
`<button class="ausleihen disabled-button" disabled>${item.BlockedNow ? 'Reserviert' : 'Ausgeliehen'}</button>` `<button class="ausleihen disabled-button" disabled>${item.BlockedNow ? 'Reserviert' : 'Ausgeliehen'}</button>`
} }
${canScheduleItem ? `<button class="schedule-button" type="button" onclick="openScheduleModal('${item._id}')">Termin planen</button>` : ''} ${canScheduleItem ? `<button class="schedule-button" type="button" onclick="openScheduleModal('${item._id}')">Reservieren</button>` : ''}
<button class="quick-details-button" type="button" onclick="openItemQuick('${item._id}')">Details</button> <button class="quick-details-button" type="button" onclick="openItemQuick('${item._id}')">Details</button>
</div> </div>
`; `;
@@ -1734,7 +1734,7 @@
</form>` </form>`
: `<button class="ausleihen disabled-button" disabled>${item.BlockedNow ? 'Reserviert' : 'Ausgeliehen'}</button>` : `<button class="ausleihen disabled-button" disabled>${item.BlockedNow ? 'Reserviert' : 'Ausgeliehen'}</button>`
} }
${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Termin planen</button>` : ''} ${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Reservieren</button>` : ''}
<button type="button" class="details-button" onclick="document.getElementById('item-modal').style.display='none';">Schließen</button> <button type="button" class="details-button" onclick="document.getElementById('item-modal').style.display='none';">Schließen</button>
</div> </div>
`; `;
+9 -3
View File
@@ -2643,7 +2643,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
<div id="schedule-modal" class="item-modal"> <div id="schedule-modal" class="item-modal">
<div class="modal-content"> <div class="modal-content">
<span class="close-modal" onclick="closeScheduleModal()">&times;</span> <span class="close-modal" onclick="closeScheduleModal()">&times;</span>
<h2>Termin planen</h2> <h2>Reservieren</h2>
<form id="schedule-form"> <form id="schedule-form">
<input type="hidden" id="schedule-item-id" name="item_id"> <input type="hidden" id="schedule-item-id" name="item_id">
<div class="form-group" id="schedule-specific-item-group" style="display:none;"> <div class="form-group" id="schedule-specific-item-group" style="display:none;">
@@ -3252,6 +3252,12 @@ document.addEventListener('DOMContentLoaded', ()=>{
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const openItem = urlParams.get('open_item_modal');
if (openItem) {
openEditModalFromServer(openItem);
}
// Load saved filters // Load saved filters
loadFilterState(); loadFilterState();
@@ -3654,7 +3660,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
</form> </form>
<button class="edit-button" onclick="openEditModalForSelectedUnit('${item._id}', 'specific-item-card-${item._id}')">Bearbeiten</button> <button class="edit-button" onclick="openEditModalForSelectedUnit('${item._id}', 'specific-item-card-${item._id}')">Bearbeiten</button>
<button class="duplicate-button" onclick="duplicateItem('${item._id}')">Duplizieren</button> <button class="duplicate-button" onclick="duplicateItem('${item._id}')">Duplizieren</button>
${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Termin planen</button>` : ''} ${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Reservieren</button>` : ''}
</div> </div>
`; `;
itemsContainer.appendChild(card); itemsContainer.appendChild(card);
@@ -4531,7 +4537,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
<button class="edit-button" onclick="openEditModalForSelectedUnit('${item._id}', 'specific-item-modal-${item._id}')">Bearbeiten</button> <button class="edit-button" onclick="openEditModalForSelectedUnit('${item._id}', 'specific-item-modal-${item._id}')">Bearbeiten</button>
<button class="duplicate-button" onclick="duplicateItem('${item._id}')">Duplizieren</button> <button class="duplicate-button" onclick="duplicateItem('${item._id}')">Duplizieren</button>
${damageReports.length > 0 ? `<button class="damage-button" onclick="markDamageAsRepaired('${item._id}')">Repariert</button>` : `<button class="damage-button" onclick="registerDamage('${item._id}')">Schaden melden</button>`} ${damageReports.length > 0 ? `<button class="damage-button" onclick="markDamageAsRepaired('${item._id}')">Repariert</button>` : `<button class="damage-button" onclick="registerDamage('${item._id}')">Schaden melden</button>`}
${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Termin planen</button>` : ''} ${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Reservieren</button>` : ''}
<form method="POST" action="/delete_item/${item._id}" style="display:inline;" onsubmit="return confirm('Sind Sie sicher?')"> <form method="POST" action="/delete_item/${item._id}" style="display:inline;" onsubmit="return confirm('Sind Sie sicher?')">
<button class="delete-button" type="submit">Löschen</button> <button class="delete-button" type="submit">Löschen</button>
</form> </form>
+4
View File
@@ -97,6 +97,10 @@ def get_tenant_config(tenant_id=None):
if tenant_id in TENANT_REGISTRY: if tenant_id in TENANT_REGISTRY:
return TENANT_REGISTRY[tenant_id] or {} return TENANT_REGISTRY[tenant_id] or {}
for alias in _tenant_db_aliases(tenant_id):
if alias in TENANT_REGISTRY:
return TENANT_REGISTRY[alias] or {}
return TENANT_REGISTRY.get('default', {}) or {} return TENANT_REGISTRY.get('default', {}) or {}