Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions invenio_administration/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from invenio_administration.menu import AdminMenu

from .error_handlers import register_blueprint_error_handlers
from .views.base import AdminFormView, AdminResourceDetailView, AdminView


Expand Down Expand Up @@ -89,6 +90,7 @@ def create_blueprint(self):
template_folder="templates",
static_folder="static",
)
register_blueprint_error_handlers(self.blueprint)

@property
def views(self):
Expand Down
126 changes: 126 additions & 0 deletions invenio_administration/error_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# SPDX-FileCopyrightText: 2026 CERN.
# SPDX-License-Identifier: MIT

"""Administration HTTP error page handlers."""

from flask import current_app, render_template, request, url_for
from invenio_i18n import lazy_gettext as _
from invenio_theme import views as theme_views
from werkzeug.exceptions import HTTPException

_ERROR_CONTENT = {
Comment thread
sakshamarora1 marked this conversation as resolved.
401: {
"title": _("Sign in required"),
"message": _(
"You need to sign in before you can continue in the administration panel."
),
},
403: {
"title": _("Access denied"),
"message": _("You do not have permission to access this administration page."),
},
404: {
"title": _("Page not found"),
"message": _("The administration page you requested could not be found."),
},
429: {
"title": _("Too many requests"),
"message": _(
"Too many requests were sent from your session. Please try again in a moment."
),
},
500: {
"title": _("Something went wrong"),
"message": _(
"An unexpected error occurred while loading the administration panel."
),
},
}

_ADMIN_ERROR_CODES = (401, 403, 404, 429, 500)


def _is_administration_request():
"""Check if the current request targets the administration UI."""
admin_ext = current_app.extensions.get("invenio-administration")
if not admin_ext:
return False

admin_url = admin_ext.administration.url.rstrip("/")
request_path = request.path.rstrip("/")

if request_path == admin_url:
return True

return bool(admin_url and request_path.startswith(f"{admin_url}/"))


def _render_administration_error(error):
"""Render the administration error page."""
status_code = error.code if isinstance(error, HTTPException) else 500
content = _ERROR_CONTENT.get(status_code, _ERROR_CONTENT[500])

if status_code >= 500:
current_app.logger.exception("Administration panel error")

admin_ext = current_app.extensions["invenio-administration"]
dashboard_endpoint = f"{admin_ext.administration.endpoint}.dashboard"

return (
render_template(
"invenio_administration/error.html",
title=content["title"],
error_code=status_code,
error_message=content["message"],
back_url=url_for(dashboard_endpoint),
),
status_code,
)


def _administration_error_or_theme(error, theme_handler):
"""Render admin errors in the admin shell; keep theme handlers elsewhere."""
if _is_administration_request():
return _render_administration_error(error)

return theme_handler(error)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in which cases we need this? the error handlder is on the administration blueprint no?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is for safety. This function is called by global error handlers, so it can run on admin and non-admin pages. The check makes sure we show admin error page only on admin URLs. Without it, normal pages could also show admin error layout.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but this is added on the specific blueprint, how is this catching global errors?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not only blueprint level. In this PR we are also registering handlers in ext.py through register_administration_error_handlers(app), and that uses app.register_error_handler(), so it is app wide fallback logic. That means this helper can run outside admin routes too. We keep _administration_error_or_theme() so admin URLs get admin layout and non admin URLs keep theme layout.



def register_blueprint_error_handlers(blueprint):
"""Register error handlers on the administration blueprint."""
# Flask resolves code-specific handlers before generic exception handlers.
# invenio-theme registers app-level 403/404/etc., so blueprint handlers must
# be registered per status code to take precedence inside admin views.
for code in _ADMIN_ERROR_CODES:
blueprint.register_error_handler(code, _render_administration_error)

blueprint.register_error_handler(HTTPException, _render_administration_error)
blueprint.register_error_handler(Exception, _render_administration_error)


def register_administration_error_handlers(app):
"""Register app-level handlers for admin URLs without a matching view."""
app.register_error_handler(
401,
lambda error: _administration_error_or_theme(error, theme_views.unauthorized),
)
app.register_error_handler(
403,
lambda error: _administration_error_or_theme(
error, theme_views.insufficient_permissions
),
)
app.register_error_handler(
404,
lambda error: _administration_error_or_theme(error, theme_views.page_not_found),
)
app.register_error_handler(
429,
lambda error: _administration_error_or_theme(
error, theme_views.too_many_requests
),
)
app.register_error_handler(
500,
lambda error: _administration_error_or_theme(error, theme_views.internal_error),
)
2 changes: 2 additions & 0 deletions invenio_administration/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from . import config
from .admin import Administration
from .error_handlers import register_administration_error_handlers
from .views.base import AdminResourceBaseView, AdminView


Expand Down Expand Up @@ -116,3 +117,4 @@ def finalize_app(app):
view_class.set_schema(extension_name=extension_name)

app.extensions["invenio-administration"].administration.init_menu()
register_administration_error_handlers(app)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{#
SPDX-FileCopyrightText: 2026 CERN.
SPDX-License-Identifier: MIT
#}

{% extends config.ADMINISTRATION_BASE_TEMPLATE %}

{% block page_title %}
<h1 class="ui header">
<i class="bolt icon" aria-hidden="true"></i>
{{ title }}
</h1>
<div class="ui divider" aria-hidden="true"></div>
{% endblock page_title %}

{% block admin_page_content %}
<p>{{ error_message }}</p>
<a class="ui button primary rel-mt-2" href="{{ back_url }}">
{{ _("Back to administration page") }}
</a>
{% endblock admin_page_content %}
Loading