import traceback import jinja2 from flask import render_template, request, jsonify, redirect, current_app, flash from flask_login import current_user from common.utils.eveai_exceptions import EveAINoSessionTenant from common.utils.nginx_utils import prefixed_url_for def not_found_error(error): if not current_user.is_authenticated: return redirect(prefixed_url_for('security.login')) current_app.logger.error(f"Not Found Error: {error}") current_app.logger.error(traceback.format_exc()) return render_template('error/404.html'), 404 def internal_server_error(error): if not current_user.is_authenticated: return redirect(prefixed_url_for('security.login')) current_app.logger.error(f"Internal Server Error: {error}") current_app.logger.error(traceback.format_exc()) return render_template('error/500.html'), 500 def not_authorised_error(error): if not current_user.is_authenticated: return redirect(prefixed_url_for('security.login')) current_app.logger.error(f"Not Authorised Error: {error}") current_app.logger.error(traceback.format_exc()) return render_template('error/401.html') def access_forbidden(error): if not current_user.is_authenticated: return redirect(prefixed_url_for('security.login')) current_app.logger.error(f"Access Forbidden: {error}") current_app.logger.error(traceback.format_exc()) return render_template('error/403.html') def key_error_handler(error): # Check if the KeyError is specifically for 'tenant' if str(error) == "'tenant'": return redirect(prefixed_url_for('security.login')) # For other KeyErrors, you might want to log the error and return a generic error page current_app.logger.error(f"Key Error: {error}") current_app.logger.error(traceback.format_exc()) return render_template('error/generic.html', error_message="An unexpected error occurred"), 500 def attribute_error_handler(error): """Handle AttributeError exceptions. Specifically catches SQLAlchemy relationship errors when string IDs are used instead of model instances. """ error_msg = str(error) current_app.logger.error(f"AttributeError: {error_msg}") current_app.logger.error(traceback.format_exc()) # Handle the SQLAlchemy relationship error specifically if "'str' object has no attribute '_sa_instance_state'" in error_msg: flash('Database relationship error. Please check your form inputs and try again.', 'error') return render_template('error/500.html', error_type="Relationship Error", error_details="A string value was provided where a database object was expected."), 500 # Handle other AttributeErrors flash('An application error occurred. The technical team has been notified.', 'error') return render_template('error/500.html', error_type="Attribute Error", error_details=error_msg), 500 def no_tenant_selected_error(error): """Handle errors when no tenant is selected in the current session. This typically happens when a session expires or becomes invalid after a long period of inactivity. The user will be redirected to the login page. """ current_app.logger.error(f"No Session Tenant Error: {error}") current_app.logger.error(traceback.format_exc()) flash('Your session expired. You will have to re-enter your credentials', 'warning') # Perform logout if user is authenticated if current_user.is_authenticated: from flask_security.utils import logout_user logout_user() # Redirect to login page return redirect(prefixed_url_for('security.login')) def general_exception(e): current_app.logger.error(f"Unhandled Exception: {e}", exc_info=True) flash('An application error occurred. The technical team has been notified.', 'error') return render_template('error/500.html', error_type=type(e).__name__, error_details=str(e)), 500 def template_not_found_error(error): """Handle Jinja2 TemplateNotFound exceptions.""" current_app.logger.error(f'Template not found: {error.name}') current_app.logger.error(f'Search Paths: {current_app.jinja_loader.list_templates()}') current_app.logger.error(traceback.format_exc()) return render_template('error/500.html', error_type="Template Not Found", error_details=f"Template '{error.name}' could not be found."), 404 def template_syntax_error(error): """Handle Jinja2 TemplateSyntaxError exceptions.""" current_app.logger.error(f'Template syntax error: {error.message}') current_app.logger.error(f'In template {error.filename}, line {error.lineno}') current_app.logger.error(traceback.format_exc()) return render_template('error/500.html', error_type="Template Syntax Error", error_details=f"Error in template '{error.filename}' at line {error.lineno}: {error.message}"), 500 def register_error_handlers(app): app.register_error_handler(404, not_found_error) app.register_error_handler(500, internal_server_error) app.register_error_handler(401, not_authorised_error) app.register_error_handler(403, not_authorised_error) app.register_error_handler(EveAINoSessionTenant, no_tenant_selected_error) app.register_error_handler(KeyError, key_error_handler) app.register_error_handler(AttributeError, attribute_error_handler) app.register_error_handler(jinja2.TemplateNotFound, template_not_found_error) app.register_error_handler(jinja2.TemplateSyntaxError, template_syntax_error) app.register_error_handler(Exception, general_exception)