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. Tests for common FreedomBox views.
""" """
import json
import pytest import pytest
from django.http import JsonResponse
from django.urls import resolve 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): 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): def test_is_safe_url_invalid_url(url):
"""Test invalid URLs for safe URL checks.""" """Test invalid URLs for safe URL checks."""
assert not is_safe_url(url) 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. Main FreedomBox views.
""" """
import functools
import logging
import random import random
import time import time
import traceback import traceback
import urllib.parse import urllib.parse
from collections.abc import Callable
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured 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 from . import forms, frontpage, operation, setup
logger = logging.getLogger(__name__)
REDIRECT_FIELD_NAME = 'next' REDIRECT_FIELD_NAME = 'next'
@ -683,6 +688,7 @@ class AppLogsView(TemplateView):
def notification_dismiss(request, id): def notification_dismiss(request, id):
"""Dismiss a notification.""" """Dismiss a notification."""
from django.http import HttpResponse from django.http import HttpResponse
from .notification import Notification from .notification import Notification
notes = Notification.list(key=id, user=request.user) notes = Notification.list(key=id, user=request.user)
if notes: if notes:
@ -697,3 +703,24 @@ def notification_dismiss(request, id):
return response return response
return HttpResponseRedirect(_get_redirect_url_from_param(request)) 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