views: Add a decorator to handle exceptions in JSON views

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2026-03-26 16:53:47 -07:00 committed by James Valleroy
parent cc626be728
commit 46f13b2be9
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
2 changed files with 59 additions and 1 deletions

View File

@ -3,10 +3,13 @@
Tests for common FreedomBox views.
"""
import json
import pytest
from django.http import JsonResponse
from django.urls import resolve
from plinth.views import get_breadcrumbs, is_safe_url
from plinth.views import get_breadcrumbs, is_safe_url, json_exception
def test_get_breadcrumbs(rf, test_menu):
@ -82,3 +85,31 @@ def test_is_safe_url_valid_url(url):
def test_is_safe_url_invalid_url(url):
"""Test invalid URLs for safe URL checks."""
assert not is_safe_url(url)
def test_json_exception(rf):
"""Test handling exceptions in JSON views."""
@json_exception
def error_view(request):
raise Exception('exception-message')
assert error_view.__name__ == 'error_view'
request = rf.get('')
response = error_view(request)
assert response.status_code == 500
json_content = json.loads(response.content)
assert not json_content['result']
assert json_content['error_name'] == 'Exception'
assert json_content['error_string'] == 'exception-message'
def success_view(request):
return JsonResponse({'result': True, 'error_string': None})
request = rf.get('')
response = success_view(request)
assert response.status_code == 200
json_content = json.loads(response.content)
assert json_content['result']
assert json_content['error_string'] is None

View File

@ -3,10 +3,13 @@
Main FreedomBox views.
"""
import functools
import logging
import random
import time
import traceback
import urllib.parse
from collections.abc import Callable
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
@ -33,6 +36,8 @@ from plinth.translation import get_language_from_request, set_language
from . import forms, frontpage, operation, setup
logger = logging.getLogger(__name__)
REDIRECT_FIELD_NAME = 'next'
@ -683,6 +688,7 @@ class AppLogsView(TemplateView):
def notification_dismiss(request, id):
"""Dismiss a notification."""
from django.http import HttpResponse
from .notification import Notification
notes = Notification.list(key=id, user=request.user)
if notes:
@ -697,3 +703,24 @@ def notification_dismiss(request, id):
return response
return HttpResponseRedirect(_get_redirect_url_from_param(request))
def json_exception(view_func: Callable):
"""View decorator to return an view exception in JSON format."""
@functools.wraps(view_func)
def wrapper(*args, **kwargs):
"""Wrapper over view function to handle exceptions."""
try:
return view_func(*args, **kwargs)
except Exception as exception:
logger.exception('Error running view %s - %s', view_func.__name__,
exception)
return JsonResponse(
{
'result': False,
'error_name': type(exception).__name__,
'error_string': str(exception)
}, status=500) # Internal server error
return wrapper