From f377840f210bf7b032b80ea474d0f1f0b2ccd33e Mon Sep 17 00:00:00 2001 From: Maffooch <46459665+Maffooch@users.noreply.github.com> Date: Tue, 1 Sep 2026 00:23:17 +0000 Subject: [PATCH] fix(login): render login page when Site row for SITE_ID is missing DojoLoginView inherited Django's LoginView.get_context_data(), which resolves the current Site via get_current_site() and raises Site.DoesNotExist -- an HTTP 500 -- when the django_site row referenced by SITE_ID is absent on an instance. Because this happens on the anonymous sign-in page itself, it locks users out entirely. Override get_context_data() to resolve the current site defensively, falling back to RequestSite(request) when the row is missing, mirroring Django's behaviour when the sites framework is not installed. The Site is only used for the site/site_name template context, so a missing row must never take the page down. No schema change. Add unittests/test_login_view_missing_site.py covering the site-present (control) and site-missing (regression) cases. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014MhRZa88XXg3GHhpvz3pEk --- dojo/user/ui/views.py | 24 +++++++++++++++++ unittests/test_login_view_missing_site.py | 32 +++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 unittests/test_login_view_missing_site.py diff --git a/dojo/user/ui/views.py b/dojo/user/ui/views.py index e5da6b4487c..cb1d9468150 100644 --- a/dojo/user/ui/views.py +++ b/dojo/user/ui/views.py @@ -11,6 +11,9 @@ from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm from django.contrib.auth.views import LoginView, PasswordResetConfirmView, PasswordResetView from django.contrib.humanize.templatetags.humanize import naturaltime +from django.contrib.sites.models import Site +from django.contrib.sites.requests import RequestSite +from django.contrib.sites.shortcuts import get_current_site from django.core import serializers from django.core.exceptions import PermissionDenied, ValidationError from django.core.mail import get_connection @@ -59,6 +62,27 @@ class DojoLoginView(LoginView): template_name = "dojo/login.html" authentication_form = AuthenticationForm + def get_context_data(self, **kwargs): + # Django's LoginView.get_context_data() resolves the current Site via + # get_current_site(), which raises Site.DoesNotExist (surfacing as an + # HTTP 500 on the login page) when the django_site row referenced by + # SITE_ID is absent on an instance. Resolve the site defensively and + # fall back to a request-derived site -- mirroring what Django itself + # does when the sites framework is not installed -- so the login page + # always renders instead of 500ing. + try: + current_site = get_current_site(self.request) + except Site.DoesNotExist: + current_site = RequestSite(self.request) + context = super(LoginView, self).get_context_data(**kwargs) + context.update({ + self.redirect_field_name: self.get_redirect_url(), + "site": current_site, + "site_name": current_site.name, + **(self.extra_context or {}), + }) + return context + def form_valid(self, form): last_login = None with contextlib.suppress(Exception): diff --git a/unittests/test_login_view_missing_site.py b/unittests/test_login_view_missing_site.py new file mode 100644 index 00000000000..e18ffd62ef0 --- /dev/null +++ b/unittests/test_login_view_missing_site.py @@ -0,0 +1,32 @@ +from django.contrib.sites.models import Site +from django.test import TestCase, override_settings + + +# Regression: GET /login returned HTTP 500 (Site.DoesNotExist) on instances +# whose django_site row referenced by SITE_ID was absent. Django's LoginView +# resolves the current Site while building the login page context, and raises +# when that row is missing; the login page must render regardless. +@override_settings(SECURE_SSL_REDIRECT=False) +class TestLoginViewMissingSite(TestCase): + + def test_login_page_renders_when_site_present(self): + # Control case: the default Site row exists, login renders normally. + self.assertTrue(Site.objects.exists()) + response = self.client.get("/login") + self.assertEqual( + response.status_code, 200, + msg=f"login GET returned {response.status_code} with a Site row present", + ) + + def test_login_page_renders_when_site_row_missing(self): + # Reproduce the reported condition: no django_site row matches SITE_ID. + Site.objects.all().delete() + self.assertFalse(Site.objects.exists()) + response = self.client.get("/login") + self.assertEqual( + response.status_code, 200, + msg=( + f"login GET returned {response.status_code} when the Site row is " + "missing (expected 200, not a Site.DoesNotExist 500)" + ), + )