Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions dojo/user/ui/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
32 changes: 32 additions & 0 deletions unittests/test_login_view_missing_site.py
Original file line number Diff line number Diff line change
@@ -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)"
),
)
Loading