diff mbox series

[layerindex-web] templates: fix account activation for django-registration 5

Message ID 20261018213233.65737-1-tim.orling@konsulko.com
State New
Headers show
Series [layerindex-web] templates: fix account activation for django-registration 5 | expand

Commit Message

Tim Orling Oct. 18, 2026, 9:32 p.m. UTC
Since the Django 5.2 upgrade (23fdeac) pulled in django-registration
5.x, sending an activation email fails with NoReverseMatch: the
template passed the activation key as a URL argument, but the
"django_registration_activate" URL now takes it as the activation_key
query parameter. New registrations, and users changing their email
address on the profile page, were left deactivated with no email sent.

The activation view is now a form that POSTs the key back and renders
django_registration/activation_form.html, so replace the old
activate.html and activation_failed.html templates with it.

AI-Generated: Claude Opus 5.5
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
 templates/django_registration/activate.html   | 18 -------
 .../activation_email_body.txt                 |  2 +-
 .../activation_failed.html                    | 13 -----
 .../django_registration/activation_form.html  | 21 ++++++++
 tests/test_account_activation.py              | 52 +++++++++++++++++++
 5 files changed, 74 insertions(+), 32 deletions(-)
 delete mode 100644 templates/django_registration/activate.html
 delete mode 100644 templates/django_registration/activation_failed.html
 create mode 100644 templates/django_registration/activation_form.html
 create mode 100644 tests/test_account_activation.py


base-commit: e6040597ae0f614581de241fe78f20e34dae13ac
diff mbox series

Patch

diff --git a/templates/django_registration/activate.html b/templates/django_registration/activate.html
deleted file mode 100644
index 39c2bac..0000000
--- a/templates/django_registration/activate.html
+++ /dev/null
@@ -1,18 +0,0 @@ 
-{% extends "base.html" %}
-{% load i18n %}
-
-{% block content %}
-
-{% if account %}
-
-<p>{% trans "Account successfully activated" %}</p>
-
-<p><a href="{% url "login" %}">{% trans "Log in" %}</a></p>
-
-{% else %}
-
-<p>{% trans "Account activation failed" %}</p>
-
-{% endif %}
-
-{% endblock %}
diff --git a/templates/django_registration/activation_email_body.txt b/templates/django_registration/activation_email_body.txt
index 15dd99b..7061136 100644
--- a/templates/django_registration/activation_email_body.txt
+++ b/templates/django_registration/activation_email_body.txt
@@ -7,7 +7,7 @@  link is valid for {{ expiration_days }} days.
 
 {% endblocktrans %}
 
-http://{{ site.domain }}{% url "django_registration_activate" activation_key %}
+http://{{ site.domain }}{% url "django_registration_activate" %}?activation_key={{ activation_key|urlencode }}
 
 {% blocktrans %}
 If you did not make this request, please ignore this message.
diff --git a/templates/django_registration/activation_failed.html b/templates/django_registration/activation_failed.html
deleted file mode 100644
index 07b0cde..0000000
--- a/templates/django_registration/activation_failed.html
+++ /dev/null
@@ -1,13 +0,0 @@ 
-{% extends "base.html" %}
-{% load i18n %}
-
-{% block content %}
-
-{% blocktrans %}
-
-Activation has failed.
-<br/>
-
-{% endblocktrans %}
-
-{% endblock %}
diff --git a/templates/django_registration/activation_form.html b/templates/django_registration/activation_form.html
new file mode 100644
index 0000000..ea4e38f
--- /dev/null
+++ b/templates/django_registration/activation_form.html
@@ -0,0 +1,21 @@ 
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+{% if activation_error %}
+<div class="alert alert-danger">{{ activation_error.message }}</div>
+{% elif form.errors %}
+<div class="alert alert-danger">{% trans "This activation link is invalid or has expired." %}</div>
+{% else %}
+<p>{% trans "Click the button below to activate your account." %}</p>
+<form id="activation_form" method="post" action="">
+    {% for field in form %}
+        <input type="hidden" name="{{ field.html_name }}" value="{{ field.value|default_if_none:'' }}" />
+    {% endfor %}
+    <input type="submit" class="btn btn-default" value="{% trans 'Activate account' %}" />
+    {% csrf_token %}
+</form>
+{% endif %}
+
+{% endblock %}
diff --git a/tests/test_account_activation.py b/tests/test_account_activation.py
new file mode 100644
index 0000000..3fb56f7
--- /dev/null
+++ b/tests/test_account_activation.py
@@ -0,0 +1,52 @@ 
+# layerindex-web - tests for the account activation email and page
+#
+# Licensed under the MIT license, see COPYING.MIT for details
+#
+# SPDX-License-Identifier: MIT
+
+import re
+
+import pytest
+from django.contrib.auth.models import User
+from django.core import mail
+from django.test import RequestFactory, TestCase
+from django.urls import reverse
+from django_registration.backends.activation.views import RegistrationView
+
+
+@pytest.mark.django_db
+class TestAccountActivation(TestCase):
+
+    def setUp(self):
+        self.user = User.objects.create_user('newuser', 'new@example.com', 'pw', is_active=False)
+
+    def _activation_path(self):
+        view = RegistrationView()
+        view.request = RequestFactory().get('/')
+        view.send_activation_email(self.user)
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].to, ['new@example.com'])
+        match = re.search(r'https?://[^/\s]+(/\S+)', mail.outbox[0].body)
+        self.assertIsNotNone(match, mail.outbox[0].body)
+        return match.group(1)
+
+    def test_activation_link_activates_account(self):
+        path = self._activation_path()
+        self.assertTrue(path.startswith(reverse('django_registration_activate') + '?activation_key='))
+
+        response = self.client.get(path)
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, 'activation_form')
+
+        key = response.context['form']['activation_key'].value()
+        response = self.client.post(reverse('django_registration_activate'), {'activation_key': key})
+        self.assertRedirects(response, reverse('django_registration_activation_complete'))
+        self.user.refresh_from_db()
+        self.assertTrue(self.user.is_active)
+
+    def test_invalid_key_does_not_activate(self):
+        response = self.client.post(reverse('django_registration_activate'), {'activation_key': 'bogus'})
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, 'invalid or has expired')
+        self.user.refresh_from_db()
+        self.assertFalse(self.user.is_active)