diff --git a/apps/auth/templates/auth/password_reset.html b/apps/auth/templates/auth/password_reset.html new file mode 100644 index 00000000..8af5626f --- /dev/null +++ b/apps/auth/templates/auth/password_reset.html @@ -0,0 +1,2 @@ +Someone asked for password reset for email {{ email }}. Follow the link below: +{{ protocol}}://{{ domain }}{% url 'lilcity:password_reset_confirm' uidb64=uid token=token %} \ No newline at end of file diff --git a/apps/auth/templates/auth/password_reset_confirm.html b/apps/auth/templates/auth/password_reset_confirm.html new file mode 100644 index 00000000..44d9d64f --- /dev/null +++ b/apps/auth/templates/auth/password_reset_confirm.html @@ -0,0 +1,12 @@ +{% if validlink %} +

Change password

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% else %} +

+ The password reset link was invalid, possibly because it has already been used. + Please request a new password reset. +

\ No newline at end of file diff --git a/apps/auth/templates/auth/password_reset_subject.txt b/apps/auth/templates/auth/password_reset_subject.txt new file mode 100644 index 00000000..bca0a5e0 --- /dev/null +++ b/apps/auth/templates/auth/password_reset_subject.txt @@ -0,0 +1 @@ +Password reset \ No newline at end of file diff --git a/apps/auth/tests/test_password_reset.py b/apps/auth/tests/test_password_reset.py new file mode 100644 index 00000000..4c1c8559 --- /dev/null +++ b/apps/auth/tests/test_password_reset.py @@ -0,0 +1,20 @@ +from django.test import TestCase, Client +from django.urls import reverse +from django.core import mail +from django.contrib.auth import get_user_model + + +User = get_user_model() + + +class PasswordResetTest(TestCase): + def setUp(self): + self.url = reverse("lilcity:password_reset") + self.user = User.objects.create_user(username='Alice', email="example@example.com", password='1234') + self.client = Client() + + def test_send_email_when_password_reset(self): + response = self.client.post(self.url, {"email": "example@example.com"}) + + self.assertEqual(True, response.json()["success"]) + self.assertEqual(len(mail.outbox), 1) diff --git a/apps/auth/urls.py b/apps/auth/urls.py index fc8dfe57..d893e6c7 100644 --- a/apps/auth/urls.py +++ b/apps/auth/urls.py @@ -7,4 +7,8 @@ urlpatterns = [ path('logout/', views.LogoutView.as_view(), name="logout"), path('login/', views.LoginView.as_view(), name="login"), path('verification-email//', views.VerificationEmailView.as_view(), name="verification-email"), + + path('password_reset/', views.PasswordResetView.as_view(), name="password_reset"), + path('password_reset_confirm///', views.PasswordResetConfirmView.as_view(), name="password_reset_confirm"), + path('password_reset_complete/', views.PasswordResetComplete.as_view(), name='password_reset_complete'), ] diff --git a/apps/auth/views.py b/apps/auth/views.py index d3af957a..dee5b0d8 100644 --- a/apps/auth/views.py +++ b/apps/auth/views.py @@ -1,7 +1,9 @@ -from django.contrib.auth import get_user_model, logout, login +from django.contrib.auth import get_user_model, logout, login, views from django.contrib.auth.forms import AuthenticationForm from django.http import JsonResponse from django.views.generic import FormView, View +from django.views.generic.edit import BaseFormView +from django.urls import reverse_lazy from .forms import LearnerRegistrationForm from .tokens import verification_email_token @@ -63,3 +65,38 @@ class VerificationEmailView(View): return JsonResponse({"success": True}) else: return JsonResponse({"success": False}, status=400) + + +class PasswordResetView(views.PasswordContextMixin, BaseFormView): + email_template_name = "auth/password_reset.html" + subject_template_name = "auth/password_reset_subject.txt" + form_class = views.PasswordResetForm + + extra_email_context = None + from_email = None + html_email_template_name = None + title = 'Password reset' + token_generator = views.default_token_generator + + def form_valid(self, form): + opts = { + 'use_https': self.request.is_secure(), + 'token_generator': self.token_generator, + 'from_email': self.from_email, + 'email_template_name': self.email_template_name, + 'subject_template_name': self.subject_template_name, + 'request': self.request, + 'html_email_template_name': self.html_email_template_name, + 'extra_email_context': self.extra_email_context, + } + form.save(**opts) + return JsonResponse({"success": True}) + + +class PasswordResetConfirmView(views.PasswordResetConfirmView): + template_name = "auth/password_reset_confirm.html" + success_url = reverse_lazy('lilcity:password_reset_complete') + + +class PasswordResetComplete(views.PasswordResetCompleteView): + pass