From 1d00e7535b0de0626855212851569923768fa3c2 Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Sun, 17 May 2026 14:01:35 -0400 Subject: [PATCH 1/2] Return JSON for 404s, matching the rest of the API handler500 already routes 500s through output_json so API clients get a real error dict back. handler404 was the Django default, so any request that didn't match a URL pattern got the framework's HTML page back instead, even though the docs say MapIt always returns JSON. Mirror the pattern: add json_404 alongside json_500 and register it as handler404 in project/urls.py. Closes #421. Signed-off-by: Charlie Tonneslan --- mapit/shortcuts.py | 4 ++++ project/urls.py | 1 + 2 files changed, 5 insertions(+) diff --git a/mapit/shortcuts.py b/mapit/shortcuts.py index 62f9427b..60400022 100644 --- a/mapit/shortcuts.py +++ b/mapit/shortcuts.py @@ -101,6 +101,10 @@ def json_500(request): return output_json({'error': "Sorry, something's gone wrong."}, code=500) +def json_404(request, exception=None): + return output_json({'error': "Not found."}, code=404) + + def set_timeout(format): cursor = connection.cursor() timeout = 10000 if format == 'html' else 10000 diff --git a/project/urls.py b/project/urls.py index f0c05566..674177c6 100644 --- a/project/urls.py +++ b/project/urls.py @@ -1,6 +1,7 @@ from django.urls import include, path from django.contrib import admin +handler404 = 'mapit.shortcuts.json_404' handler500 = 'mapit.shortcuts.json_500' urlpatterns = [ From c28cd0898e1a9b78f0ed218b147adee30120c4b7 Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Mon, 18 May 2026 09:40:34 -0400 Subject: [PATCH 2/2] Narrow JSON 404 to API URL prefixes Drop the global handler404 and instead match catch-all patterns for the API URL prefixes (postcode, area, areas, point, nearest, code, generations, types) at the end of mapit/urls.py. Non-API URLs keep falling through to the project-level HTML 404 page. Signed-off-by: Charlie Tonneslan --- mapit/shortcuts.py | 2 +- mapit/urls.py | 7 +++++++ project/urls.py | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mapit/shortcuts.py b/mapit/shortcuts.py index 60400022..4fa2340b 100644 --- a/mapit/shortcuts.py +++ b/mapit/shortcuts.py @@ -101,7 +101,7 @@ def json_500(request): return output_json({'error': "Sorry, something's gone wrong."}, code=500) -def json_404(request, exception=None): +def json_404(request, *args, **kwargs): return output_json({'error': "Not found."}, code=404) diff --git a/mapit/urls.py b/mapit/urls.py index 53e5cd4a..9e5546de 100644 --- a/mapit/urls.py +++ b/mapit/urls.py @@ -2,6 +2,7 @@ from django.conf import settings from django.shortcuts import render +from mapit.shortcuts import json_404 from mapit.utils import re_number as number from mapit.views import areas, postcodes @@ -58,6 +59,12 @@ re_path(r'^areas/(?P.+?)%s$' % map_format_end, areas.areas_by_name), re_path(r'^areas$', areas.deal_with_POST, {'call': 'areas'}), re_path(r'^code/(?P[^/]+)/(?P[^/]+?)%s$' % format_end, areas.area_from_code), + + # Catch API-shaped URLs that didn't match any of the specific patterns + # above and return a JSON 404 instead of Django's HTML one. Non-API URLs + # still fall through to the project-level 404 page. + re_path(r'^(?:postcode|area|areas|point|nearest|code)(?:/.*|\.[a-z]+)?$', json_404), + re_path(r'^(?:generations|types)(?:\.[a-z]+)?$', json_404), ] # Include app-specific urls diff --git a/project/urls.py b/project/urls.py index 674177c6..f0c05566 100644 --- a/project/urls.py +++ b/project/urls.py @@ -1,7 +1,6 @@ from django.urls import include, path from django.contrib import admin -handler404 = 'mapit.shortcuts.json_404' handler500 = 'mapit.shortcuts.json_500' urlpatterns = [