From 42bcd5d955e7fe8af591426bea5ad76384fb9464 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:22:59 +0000 Subject: [PATCH] Changes before error encountered Co-authored-by: vanschelven <223833+vanschelven@users.noreply.github.com> --- bugsink/app_settings.py | 17 +++++ bugsink/context_processors.py | 3 +- bugsink/settings/default.py | 7 ++ bugsink/test_subpath.py | 67 +++++++++++++++++++ issues/templates/issues/base.html | 25 +++---- issues/templates/issues/event_list.html | 5 +- issues/templates/issues/issue_list.html | 3 +- "ith_prefix }}\"|g" | 1 + projects/templates/projects/project_list.html | 3 +- templates/bugsink/login.html | 3 +- templates/signup.html | 3 +- theme/templates/bare_base.html | 6 +- theme/templates/base.html | 16 ++--- theme/templatetags/urls.py | 27 ++++++++ users/templates/users/confirm_email.html | 3 +- users/templates/users/confirm_email_sent.html | 3 +- users/templates/users/logged_out.html | 3 +- .../users/request_reset_password.html | 3 +- .../templates/users/resend_confirmation.html | 3 +- users/templates/users/reset_password.html | 3 +- .../users/reset_password_email_sent.html | 3 +- 21 files changed, 170 insertions(+), 37 deletions(-) create mode 100644 bugsink/test_subpath.py create mode 100644 "ith_prefix }}\"|g" create mode 100644 theme/templatetags/urls.py diff --git a/bugsink/app_settings.py b/bugsink/app_settings.py index 5bd1ce4..c855b17 100644 --- a/bugsink/app_settings.py +++ b/bugsink/app_settings.py @@ -2,6 +2,7 @@ # alternative would be: just put "everything" in the big settings.py (or some mix-using-imports version of that). # but for now I like the idea of keeping the bugsink-as-an-app stuff separate from the regular Django/db/global stuff. import os +import urllib.parse from contextlib import contextmanager from django.conf import settings @@ -110,6 +111,22 @@ def _sanitize(settings): settings["TEAM_CREATION"] = CB_NOBODY +def get_path_prefix(): + """Extract the path prefix from BASE_URL for subpath hosting support.""" + base_url = get_settings().BASE_URL + parsed_url = urllib.parse.urlparse(base_url) + path = parsed_url.path + + # Ensure path starts with / and doesn't end with / (unless it's just "/") + if path and path != "/": + if not path.startswith("/"): + path = "/" + path + if path.endswith("/"): + path = path[:-1] + return path + return "" + + def get_settings(): global _settings if _settings is None: diff --git a/bugsink/context_processors.py b/bugsink/context_processors.py index 172560e..e1ea690 100644 --- a/bugsink/context_processors.py +++ b/bugsink/context_processors.py @@ -10,7 +10,7 @@ from django.contrib.auth.models import AnonymousUser from django.db.utils import OperationalError from django.db.models import Sum -from bugsink.app_settings import get_settings, CB_ANYBODY +from bugsink.app_settings import get_settings, CB_ANYBODY, get_path_prefix from bugsink.transaction import durable_atomic from bugsink.timed_sqlite_backend.base import different_runtime_limit @@ -135,6 +135,7 @@ def useful_settings_processor(request): 'site_title': get_settings().SITE_TITLE, 'registration_enabled': get_settings().USER_REGISTRATION == CB_ANYBODY, 'app_settings': get_settings(), + 'path_prefix': get_path_prefix(), 'system_warnings': get_system_warnings, } diff --git a/bugsink/settings/default.py b/bugsink/settings/default.py index f4881fe..6356a56 100644 --- a/bugsink/settings/default.py +++ b/bugsink/settings/default.py @@ -343,3 +343,10 @@ if I_AM_RUNNING == "SNAPPEA": for logger in LOGGING['loggers'].values(): if "handlers" in logger and "console" in logger["handlers"]: logger["handlers"] = ["snappea"] + +# Set FORCE_SCRIPT_NAME for subpath hosting support +# Import here to avoid circular dependency +from bugsink.app_settings import get_path_prefix +_path_prefix = get_path_prefix() +if _path_prefix: + FORCE_SCRIPT_NAME = _path_prefix diff --git a/bugsink/test_subpath.py b/bugsink/test_subpath.py new file mode 100644 index 0000000..2936c72 --- /dev/null +++ b/bugsink/test_subpath.py @@ -0,0 +1,67 @@ +from django.test import TestCase, override_settings +from django.template import Template, Context + +from bugsink.app_settings import get_path_prefix, override_settings as override_bugsink_settings +from bugsink.context_processors import useful_settings_processor + + +class SubpathHostingTestCase(TestCase): + """Test subpath hosting functionality.""" + + def test_get_path_prefix_default(self): + """Test that get_path_prefix returns empty string for default BASE_URL.""" + with override_bugsink_settings(BASE_URL="http://localhost:8000"): + self.assertEqual(get_path_prefix(), "") + + def test_get_path_prefix_subpath(self): + """Test that get_path_prefix extracts path from BASE_URL.""" + with override_bugsink_settings(BASE_URL="https://example.com/bugsink"): + self.assertEqual(get_path_prefix(), "/bugsink") + + def test_get_path_prefix_subpath_with_trailing_slash(self): + """Test that get_path_prefix handles trailing slash correctly.""" + with override_bugsink_settings(BASE_URL="https://example.com/bugsink/"): + self.assertEqual(get_path_prefix(), "/bugsink") + + def test_get_path_prefix_deeper_path(self): + """Test that get_path_prefix handles deeper paths.""" + with override_bugsink_settings(BASE_URL="https://example.com/tools/bugsink"): + self.assertEqual(get_path_prefix(), "/tools/bugsink") + + def test_context_processor_includes_path_prefix(self): + """Test that context processor includes path prefix.""" + from django.http import HttpRequest + + request = HttpRequest() + request.user = None # Mock an anonymous user + + with override_bugsink_settings(BASE_URL="https://example.com/bugsink"): + context = useful_settings_processor(request) + self.assertEqual(context['path_prefix'], "/bugsink") + + def test_template_filter_with_prefix(self): + """Test the with_prefix template filter.""" + template = Template('{% load urls %}{{ url | with_prefix }}') + + with override_bugsink_settings(BASE_URL="https://example.com/bugsink"): + context = Context({'url': '/admin/'}) + result = template.render(context) + self.assertEqual(result, "/bugsink/admin/") + + def test_template_filter_without_prefix(self): + """Test the with_prefix template filter with no prefix.""" + template = Template('{% load urls %}{{ url | with_prefix }}') + + with override_bugsink_settings(BASE_URL="http://localhost:8000"): + context = Context({'url': '/admin/'}) + result = template.render(context) + self.assertEqual(result, "/admin/") + + def test_template_filter_relative_url(self): + """Test the with_prefix template filter with relative URLs.""" + template = Template('{% load urls %}{{ url | with_prefix }}') + + with override_bugsink_settings(BASE_URL="https://example.com/bugsink"): + context = Context({'url': 'admin/'}) + result = template.render(context) + self.assertEqual(result, "admin/") # Should not add prefix to relative URLs \ No newline at end of file diff --git a/issues/templates/issues/base.html b/issues/templates/issues/base.html index e4d1704..d05329b 100644 --- a/issues/templates/issues/base.html +++ b/issues/templates/issues/base.html @@ -5,6 +5,7 @@ {% load stricter_templates %} {% load add_to_qs %} {% load i18n %} +{% load urls %} {% block title %}{{ issue.title }} · {{ block.super }}{% endblock %} @@ -109,13 +110,13 @@ {# overflow-x-auto is needed at the level of the flex item such that it works at the level where we need it (the code listings)#}
{# 96rem is 1536px, which matches the 2xl class; this is no "must" but eyeballing revealed: good result #}
-
{% translate "Stacktrace" %}
-
{% translate "Event Details" %}
-
{% translate "Breadcrumbs" %}
-
{% translate "Event List" %}
-
{% translate "Tags" %}
-
{% translate "Grouping" %}
-
{% translate "History" %}
+
{% translate "Stacktrace" %}
+
{% translate "Event Details" %}
+
{% translate "Breadcrumbs" %}
+
{% translate "Event List" %}
+
{% translate "Tags" %}
+
{% translate "Grouping" %}
+
{% translate "History" %}
@@ -127,16 +128,16 @@ {% if is_event_page %}
{% blocktranslate with digest_order=event.digest_order|intcomma total_events=issue.digested_event_count|intcomma ingested_at=event.ingested_at|date:"j M G:i T" %}Event {{ digest_order }} of {{ total_events }} which occured at {{ ingested_at }}{% endblocktranslate %}
{% endif %}
{% if is_event_page %} - {% translate "Download" %} - | {% translate "JSON" %} - | {% translate "Plain" %} + {% translate "Download" %} + | {% translate "JSON" %} + | {% translate "Plain" %} {% endif %} {% if app_settings.USE_ADMIN and user.is_staff %} {% if is_event_page %} - | {% translate "Event Admin" %} | + | {% translate "Event Admin" %} | {% endif %} - {% translate "Issue Admin" %} + {% translate "Issue Admin" %} {% endif %}
diff --git a/issues/templates/issues/event_list.html b/issues/templates/issues/event_list.html index 6ffb91c..945df52 100644 --- a/issues/templates/issues/event_list.html +++ b/issues/templates/issues/event_list.html @@ -3,6 +3,7 @@ {% load add_to_qs %} {% load humanize %} {% load add_to_qs %} +{% load urls %} {% block tab_content %} @@ -119,11 +120,11 @@ TODO - {{ event.digest_order }} + {{ event.digest_order }} {# how useful is this really? #} - {{ event.id|truncatechars:9 }} + {{ event.id|truncatechars:9 }} diff --git a/issues/templates/issues/issue_list.html b/issues/templates/issues/issue_list.html index 74ae385..e7f2b3c 100644 --- a/issues/templates/issues/issue_list.html +++ b/issues/templates/issues/issue_list.html @@ -3,6 +3,7 @@ {% load humanize %} {% load add_to_qs %} {% load i18n %} +{% load urls %} {% block title %}{% translate "Issues" %} · {{ project.name }} · {{ site_title }}{% endblock %} @@ -170,7 +171,7 @@
- {% if issue.is_resolved %} + {% if issue.is_resolved %} {% endif %}{% if issue.is_muted %}   {% endif %}{{ issue.title|truncatechars:100 }} diff --git "a/ith_prefix }}\"|g" "b/ith_prefix }}\"|g" new file mode 100644 index 0000000..e0afbe8 --- /dev/null +++ "b/ith_prefix }}\"|g" @@ -0,0 +1 @@ + Bugsink logo diff --git a/projects/templates/projects/project_list.html b/projects/templates/projects/project_list.html index bb0b89c..e6a637f 100644 --- a/projects/templates/projects/project_list.html +++ b/projects/templates/projects/project_list.html @@ -1,6 +1,7 @@ {% extends "base.html" %} {% load static %} {% load i18n %} +{% load urls %} {% block title %}{% translate "Projects" %} · {{ site_title }}{% endblock %} @@ -64,7 +65,7 @@
{% if project.member or request.user.is_superuser %} - {{ project.name }} + {{ project.name }} {% else %} {{ project.name }} {% endif %} diff --git a/templates/bugsink/login.html b/templates/bugsink/login.html index d515fc6..a7f7639 100644 --- a/templates/bugsink/login.html +++ b/templates/bugsink/login.html @@ -1,4 +1,5 @@ {% extends "barest_base.html" %} +{% load urls %} {% load static %} {% load i18n %} @@ -9,7 +10,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo
diff --git a/templates/signup.html b/templates/signup.html index ccf12a7..c02b631 100644 --- a/templates/signup.html +++ b/templates/signup.html @@ -1,4 +1,5 @@ {% extends "barest_base.html" %} +{% load urls %} {% load static %} {% load tailwind_forms %} @@ -9,7 +10,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo
diff --git a/theme/templates/bare_base.html b/theme/templates/bare_base.html index 588ddbc..357b59b 100644 --- a/theme/templates/bare_base.html +++ b/theme/templates/bare_base.html @@ -1,4 +1,4 @@ -{% load static tailwind_tags %}{# copy of base.html, but without variables (and hence no menu), for use in contextless templates (e.g. 500.html) #} +{% load static tailwind_tags urls %}{# copy of base.html, but without variables (and hence no menu), for use in contextless templates (e.g. 500.html) #} {% block title %}Bugsink{% endblock %} @@ -26,8 +26,8 @@
- Bugsink logo -
Bugsink
+ Bugsink logo +
Bugsink
{% block content %}{% endblock %} diff --git a/theme/templates/base.html b/theme/templates/base.html index 245bfce..abe378c 100644 --- a/theme/templates/base.html +++ b/theme/templates/base.html @@ -1,4 +1,4 @@ -{% load static tailwind_tags version add_to_qs %}{% load i18n %} +{% load static tailwind_tags version add_to_qs urls %}{% load i18n %} @@ -27,8 +27,8 @@
- Bugsink logo -
{{ site_title }}
+ Bugsink logo +
{{ site_title }}
{% if not app_settings.SINGLE_TEAM %}
{% translate "Teams" %}
@@ -42,18 +42,18 @@
{% if app_settings.USE_ADMIN and user.is_staff %} -
{% translate "Admin" %}
+
{% translate "Admin" %}
{% endif %} {% if user.is_superuser %} -
{% translate "Users" %}
-
{% translate "Tokens" %}
+
{% translate "Users" %}
+
{% translate "Tokens" %}
{% endif %} {% if logged_in_user.is_anonymous %} -
{% translate "Login" %}
{# I don't think this is actually ever shown in practice, because you must always be logged in #} +
{% translate "Login" %}
{# I don't think this is actually ever shown in practice, because you must always be logged in #} {% else %} -
{% translate "Preferences" %}
+
{% translate "Preferences" %}
{% csrf_token %}
{% endif %}
diff --git a/theme/templatetags/urls.py b/theme/templatetags/urls.py new file mode 100644 index 0000000..5293910 --- /dev/null +++ b/theme/templatetags/urls.py @@ -0,0 +1,27 @@ +from django import template +from bugsink.app_settings import get_path_prefix + +register = template.Library() + + +@register.filter +def with_prefix(url): + """Add the path prefix to a URL for subpath hosting support.""" + if not url: + return url + + prefix = get_path_prefix() + if not prefix: + return url + + # Handle absolute URLs that start with / + if url.startswith('/'): + return prefix + url + + return url + + +@register.simple_tag +def prefixed_url(url): + """Template tag version of the with_prefix filter.""" + return with_prefix(url) \ No newline at end of file diff --git a/users/templates/users/confirm_email.html b/users/templates/users/confirm_email.html index 7175a22..b203894 100644 --- a/users/templates/users/confirm_email.html +++ b/users/templates/users/confirm_email.html @@ -1,4 +1,5 @@ {% extends "barest_base.html" %} +{% load urls %} {% load static %} {% load i18n %} @@ -9,7 +10,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo
diff --git a/users/templates/users/confirm_email_sent.html b/users/templates/users/confirm_email_sent.html index 01adc91..a55d9f5 100644 --- a/users/templates/users/confirm_email_sent.html +++ b/users/templates/users/confirm_email_sent.html @@ -1,6 +1,7 @@ {% extends "barest_base.html" %} {% load static %} {% load i18n %} +{% load urls %} {% block title %}Verification email sent · {{ site_title }}{% endblock %} @@ -9,7 +10,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo
diff --git a/users/templates/users/logged_out.html b/users/templates/users/logged_out.html index 12e52b1..e010fe6 100644 --- a/users/templates/users/logged_out.html +++ b/users/templates/users/logged_out.html @@ -1,6 +1,7 @@ {% extends "barest_base.html" %} {% load static %} {% load i18n %} +{% load urls %} {% block title %}Logged out · {{ site_title }}{% endblock %} @@ -9,7 +10,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo
diff --git a/users/templates/users/request_reset_password.html b/users/templates/users/request_reset_password.html index 9704c53..c436508 100644 --- a/users/templates/users/request_reset_password.html +++ b/users/templates/users/request_reset_password.html @@ -2,6 +2,7 @@ {% load static %} {% load tailwind_forms %} {% load i18n %} +{% load urls %} {% block title %}Resend confirmation · {{ site_title }}{% endblock %} @@ -10,7 +11,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo
diff --git a/users/templates/users/resend_confirmation.html b/users/templates/users/resend_confirmation.html index 85a3366..1b510b1 100644 --- a/users/templates/users/resend_confirmation.html +++ b/users/templates/users/resend_confirmation.html @@ -1,4 +1,5 @@ {% extends "barest_base.html" %} +{% load urls %} {% load static %} {% load tailwind_forms %} {% load i18n %} @@ -10,7 +11,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo
diff --git a/users/templates/users/reset_password.html b/users/templates/users/reset_password.html index 5d2eb34..06ac7a2 100644 --- a/users/templates/users/reset_password.html +++ b/users/templates/users/reset_password.html @@ -2,6 +2,7 @@ {% load static %} {% load tailwind_forms %} {% load i18n %} +{% load urls %} {% block title %}Reset password · {{ site_title }}{% endblock %} @@ -10,7 +11,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo
diff --git a/users/templates/users/reset_password_email_sent.html b/users/templates/users/reset_password_email_sent.html index 9bc7a14..d4948db 100644 --- a/users/templates/users/reset_password_email_sent.html +++ b/users/templates/users/reset_password_email_sent.html @@ -1,6 +1,7 @@ {% extends "barest_base.html" %} {% load static %} {% load i18n %} +{% load urls %} {% block title %}Password reset sent · {{ site_title }}{% endblock %} @@ -9,7 +10,7 @@
{# the cyan background #}
{# the centered box #}
{# the logo #} - Bugsink logo + Bugsink logo