diff --git a/.gitignore b/.gitignore index 4d0e455..5da3543 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /django_gas.egg-info /.coverage /htmlcov +*.pyc diff --git a/gas/gas/core/urls.py b/gas/gas/core/urls.py index 2eb4d2b..c156e07 100644 --- a/gas/gas/core/urls.py +++ b/gas/gas/core/urls.py @@ -8,5 +8,9 @@ urlpatterns = [ path('login/', views.GASLoginView.as_view(), name='login'), path('logout/', logout_then_login, {'login_url': 'gas:login'}, name='logout'), path('change-password/', views.GASPasswordChangeView.as_view(), name='change_password'), + path('reset-password-confirm///', views.GASPasswordResetConfirmView.as_view(), name='password_reset_confirm'), + path('reset-password-confirm/done/', views.GASPasswordResetCompleteView.as_view(), name='password_reset_complete'), + path('reset-password/done/', views.GASPasswordResetDoneView.as_view(), name='password_reset_done'), + path('reset-password/', views.GASPasswordResetView.as_view(), name='reset_password'), path('', views.Index.as_view(), name='index'), ] diff --git a/gas/gas/core/views.py b/gas/gas/core/views.py index 979cf7a..c8dc0dd 100644 --- a/gas/gas/core/views.py +++ b/gas/gas/core/views.py @@ -1,45 +1,72 @@ -from django.contrib.auth.views import LoginView, PasswordChangeView +from django.contrib.auth.views import ( + LoginView, + PasswordChangeView, + PasswordResetConfirmView, + PasswordResetDoneView, + PasswordResetView, +) from django.shortcuts import resolve_url from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views.generic import TemplateView - +from gas import gas_settings from gas.views import GASMixin -from gas import gas_settings + +class GASPasswordChangeView(GASMixin, PasswordChangeView): + template_name = "gas/base_form.html" + success_url = reverse_lazy("gas:index") + continue_url = reverse_lazy("gas:change_password") + title = _("Change your password") + success_message = _("Password changed.") -class GASLoginView(LoginView): +class Index(GASMixin, TemplateView): + main_menu = "index" + template_name = "gas/index.html" + roles = ("staff",) + + +class GASContextMixin: + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + css = gas_settings.MEDIA["css"] + javascript = gas_settings.MEDIA["js"] + if gas_settings.EXTRA_MEDIA: + css = css + gas_settings.EXTRA_MEDIA.get("css", []) + javascript = javascript + gas_settings.EXTRA_MEDIA.get("js", []) + ctx.update( + { + "logo_static_url": gas_settings.LOGO, + "css": css, + "js": javascript, + } + ) + return ctx + + +class GASLoginView(GASContextMixin, LoginView): template_name = "gas/login.html" def get_success_url(self): url = self.get_redirect_url() - return url or resolve_url('gas:index') - - def get_context_data(self, **kwargs): - ctx = super().get_context_data(**kwargs) - css = gas_settings.MEDIA['css'] - javascript = gas_settings.MEDIA['js'] - if gas_settings.EXTRA_MEDIA: - css = css + gas_settings.EXTRA_MEDIA.get('css', []) - javascript = javascript + gas_settings.EXTRA_MEDIA.get('js', []) - ctx.update({ - 'logo_static_url': gas_settings.LOGO, - 'css': css, - 'js': javascript, - }) - return ctx + return url or resolve_url("gas:index") -class GASPasswordChangeView(GASMixin, PasswordChangeView): - template_name = 'gas/base_form.html' - success_url = reverse_lazy('gas:index') - continue_url = reverse_lazy('gas:change_password') - title = _('Change your password') - success_message = _('Password changed.') +class GASPasswordResetView(GASContextMixin, PasswordResetView): + template_name = "gas/reset.html" + email_template_name = "email/password_reset_email.html" + success_url = reverse_lazy("gas:password_reset_done") -class Index(GASMixin, TemplateView): - main_menu = 'index' - template_name = "gas/index.html" - roles = ('staff',) +class GASPasswordResetDoneView(GASContextMixin, PasswordResetDoneView): + template_name = "gas/reset_done.html" + + +class GASPasswordResetConfirmView(GASContextMixin, PasswordResetConfirmView): + template_name = "gas/reset_confirm.html" + success_url = reverse_lazy("gas:password_reset_complete") + + +class GASPasswordResetCompleteView(GASContextMixin, TemplateView): + template_name = "gas/reset_complete.html" diff --git a/gas/templates/email/password_reset_email.html b/gas/templates/email/password_reset_email.html new file mode 100644 index 0000000..dfcf489 --- /dev/null +++ b/gas/templates/email/password_reset_email.html @@ -0,0 +1,14 @@ +{% load i18n %}{% autoescape off %} +{% blocktranslate %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktranslate %} + +{% translate "Please go to the following page and choose a new password:" %} +{% block reset_link %} +{{ protocol }}://{{ domain }}{% url 'gas:password_reset_confirm' uidb64=uid token=token %} +{% endblock %} +{% translate 'Your username, in case you’ve forgotten:' %} {{ user.get_username }} + +{% translate "Thanks for using our site!" %} + +{% blocktranslate %}The {{ site_name }} team{% endblocktranslate %} + +{% endautoescape %} diff --git a/gas/templates/gas/reset.html b/gas/templates/gas/reset.html new file mode 100644 index 0000000..baccf18 --- /dev/null +++ b/gas/templates/gas/reset.html @@ -0,0 +1,20 @@ +{% load i18n static form_tags %} + + + + + {{ gas_title }} {% trans "Login" %} + {% for cssfile in css %} + + {% endfor %} + + +

{% translate 'Forgotten your password? Enter your email address below, and we’ll email instructions for setting a new one.' %}

+
{% csrf_token %} + {% form_errors form %} + {% form_field form.email %} + + +
+ + diff --git a/gas/templates/gas/reset_complete.html b/gas/templates/gas/reset_complete.html new file mode 100644 index 0000000..41610ea --- /dev/null +++ b/gas/templates/gas/reset_complete.html @@ -0,0 +1,14 @@ +{% load i18n static form_tags %} + + + + + {{ gas_title }} {% trans "Login" %} + {% for cssfile in css %} + + {% endfor %} + + +

{% translate 'Your password was changed.' %}

+ + diff --git a/gas/templates/gas/reset_confirm.html b/gas/templates/gas/reset_confirm.html new file mode 100644 index 0000000..88590cd --- /dev/null +++ b/gas/templates/gas/reset_confirm.html @@ -0,0 +1,26 @@ +{% load i18n static form_tags %} + + + + + {{ gas_title }} {% trans "Login" %} + {% for cssfile in css %} + + {% endfor %} + + + {% if validlink %} +

{% translate "Please enter your new password twice so we can verify you typed it in correctly." %}

+
{% csrf_token %} + + {% form_errors form %} + {% form_field form.new_password1 %} + {% form_field form.new_password2 %} + + +
+ {% else %} +

{% translate "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}

+ {% endif %} + + diff --git a/gas/templates/gas/reset_done.html b/gas/templates/gas/reset_done.html new file mode 100644 index 0000000..dcbb628 --- /dev/null +++ b/gas/templates/gas/reset_done.html @@ -0,0 +1,17 @@ +{% load i18n static form_tags %} + + + + + {{ gas_title }} {% trans "Login" %} + {% for cssfile in css %} + + {% endfor %} + + +

{% translate 'We’ve emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.' %}

+

{% translate 'If you don’t receive an email, please make sure you’ve entered the address you registered with, and check your spam folder.' %}

+ + + + diff --git a/gas/tests/test_gas_views.py b/gas/tests/test_gas_views.py index 0e1e4ee..010b6e3 100644 --- a/gas/tests/test_gas_views.py +++ b/gas/tests/test_gas_views.py @@ -3,28 +3,58 @@ from django.urls import reverse from model_bakery import baker -from gas.gas.core.views import GASLoginView - class GASLoginTestCase(TestCase): def test_load(self): client = Client() - response = client.get(reverse('gas:login')) + response = client.get(reverse("gas:login")) self.assertEqual(response.status_code, 200) class IndexTestCase(TestCase): def test_load(self): admin_user = baker.make( - 'auth.User', - username='admin', + "auth.User", + username="admin", is_superuser=True, ) client = Client() - response = client.get(reverse('gas:index')) + response = client.get(reverse("gas:index")) self.assertEqual(response.status_code, 302) client.force_login(admin_user) - response = client.get(reverse('gas:index')) + response = client.get(reverse("gas:index")) self.assertEqual(response.status_code, 200) + + +class GASPasswordResetViewTestCase(TestCase): + def test_load(self): + client = Client() + response = client.get(reverse("gas:reset_password")) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "gas/reset.html") + + +class GASPasswordResetDoneViewTestCase(TestCase): + def test_load(self): + client = Client() + response = client.get(reverse("gas:password_reset_done")) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "gas/reset_done.html") + + +class GASPasswordResetConfirmViewTestCase(TestCase): + def test_load(self): + client = Client() + response = client.get(reverse("gas:password_reset_confirm", kwargs={"uidb64": "uidb64", "token": "token"})) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "gas/reset_confirm.html") + + +class GASPasswordResetCompleteViewTestCase(TestCase): + def test_load(self): + client = Client() + response = client.get(reverse("gas:password_reset_complete")) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "gas/reset_complete.html")