From cbf8c9615603259279feb8d7c1f36d0f1792c7f7 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 29 Aug 2024 07:04:22 +0200 Subject: [PATCH] Rework accounts Entirely rework the accounts front-end to simplify things and align better with built-in Django: - Use Bootstrap infrastructure to render forms plainly in templates (see previous commit). - Rename forms and views to correspond to the builtin ones, but ensure that the builtin ones can't accidentally be imported. - Use a base template for all account-related pages to ensure consistency. - Reuse the exact wording in Django templates in more places to be able to reuse Django translations seamlessly. - Avoid duplicate URLs that can be confusing if you hit the wrong one. --- app/accounts/forms.py | 49 +++++------- app/accounts/tests/test_signup_form.py | 12 +-- app/accounts/urls.py | 20 ++--- app/accounts/views.py | 51 +++++++++---- app/app/urls.py | 1 - .../password_reset_complete.html | 0 app/templates/accounts/login.html | 76 ------------------- .../accounts/password_reset_confirm.html | 50 ------------ .../accounts/password_reset_done.html | 19 ----- .../accounts/password_reset_form.html | 34 --------- app/templates/accounts/register.html | 47 ------------ app/templates/registration/login.html | 20 +++++ .../registration/password_change_done.html | 9 +++ .../registration/password_change_form2.html | 19 +++++ .../registration/password_reset_complete.html | 12 +++ .../registration/password_reset_confirm.html | 26 +++++++ .../registration/password_reset_done.html | 20 +++++ .../registration/password_reset_form.html | 19 +++++ app/templates/registration/register.html | 13 ++++ .../registration/registration_base.html | 14 ++++ 20 files changed, 227 insertions(+), 284 deletions(-) rename app/templates/{accounts => accounts.bak}/password_reset_complete.html (100%) delete mode 100644 app/templates/accounts/login.html delete mode 100644 app/templates/accounts/password_reset_confirm.html delete mode 100644 app/templates/accounts/password_reset_done.html delete mode 100644 app/templates/accounts/password_reset_form.html delete mode 100644 app/templates/accounts/register.html create mode 100644 app/templates/registration/login.html create mode 100644 app/templates/registration/password_change_done.html create mode 100644 app/templates/registration/password_change_form2.html create mode 100644 app/templates/registration/password_reset_complete.html create mode 100644 app/templates/registration/password_reset_confirm.html create mode 100644 app/templates/registration/password_reset_done.html create mode 100644 app/templates/registration/password_reset_form.html create mode 100644 app/templates/registration/register.html create mode 100644 app/templates/registration/registration_base.html diff --git a/app/accounts/forms.py b/app/accounts/forms.py index b7a13c29..b2a56acd 100644 --- a/app/accounts/forms.py +++ b/app/accounts/forms.py @@ -1,5 +1,9 @@ from django import forms -from django.contrib.auth.forms import AuthenticationForm, UserCreationForm +from django.contrib.auth.forms import AuthenticationForm as _AuthenticationForm +from django.contrib.auth.forms import PasswordChangeForm as _PasswordChangeForm +from django.contrib.auth.forms import PasswordResetForm as _PasswordResetForm +from django.contrib.auth.forms import SetPasswordForm as _SetPasswordForm +from django.contrib.auth.forms import UserCreationForm as _UserCreationForm from django.utils.translation import gettext_lazy as _ from users.models import CustomUser @@ -16,38 +20,27 @@ def __init__(self, *args, **kwargs): field.widget.attrs["class"] = "form-control" -class CustomUserCreationForm(UserCreationForm): - email = forms.EmailField( - required=True, - help_text=_("Required. Add a valid email address."), - ) - username = forms.CharField( - required=True, - help_text=_("Required. Add a valid username."), - ) - first_name = forms.CharField( - required=True, - help_text=_("Required. Add a valid first name."), - ) - last_name = forms.CharField( - required=True, - ) +class UserCreationForm(BoostrapFormMixin, _UserCreationForm): + email = forms.EmailField(label=_("E-mail address"), required=True) + first_name = forms.CharField(label=_("First name"), required=True) + last_name = forms.CharField(label=_("Last name"), required=True) class Meta: model = CustomUser fields = ("username", "email", "first_name", "last_name") - def __init__(self, *args, **kwargs): - super(CustomUserCreationForm, self).__init__(*args, **kwargs) - for field_name, field in self.fields.items(): - field.widget.attrs["class"] = "form-control" +class AuthenticationForm(BoostrapFormMixin, _AuthenticationForm): + pass -class CustomAuthenticationForm(AuthenticationForm): - username = forms.CharField(label=_("Username"), help_text=_("Required. Enter your username.")) - password = forms.CharField(label=_("Password"), help_text=_("Required. Enter your password.")) - def __init__(self, *args, **kwargs): - super(CustomAuthenticationForm, self).__init__(*args, **kwargs) - for field in self.fields.values(): - field.widget.attrs.update({"class": "form-control"}) +class PasswordChangeForm(BoostrapFormMixin, _PasswordChangeForm): + pass + + +class PasswordResetForm(BoostrapFormMixin, _PasswordResetForm): + pass + + +class SetPasswordForm(BoostrapFormMixin, _SetPasswordForm): + pass diff --git a/app/accounts/tests/test_signup_form.py b/app/accounts/tests/test_signup_form.py index 250c6dcd..9d8dac9c 100644 --- a/app/accounts/tests/test_signup_form.py +++ b/app/accounts/tests/test_signup_form.py @@ -2,10 +2,10 @@ from django.test import TestCase -from accounts.forms import CustomUserCreationForm +from accounts.forms import UserCreationForm -class CustomUserCreationFormTest(TestCase): +class UserCreationFormTest(TestCase): def setUp(self): self.username = "testuser" self.email = "testuser@gmail.com" @@ -15,7 +15,7 @@ def setUp(self): self.password2 = "sadilar2024" def test_valid_data(self): - form = CustomUserCreationForm( + form = UserCreationForm( { "username": self.username, "email": self.email, @@ -29,7 +29,7 @@ def test_valid_data(self): self.assertTrue(form.is_valid()) def test_blank_data(self): - form = CustomUserCreationForm({}) + form = UserCreationForm({}) self.assertFalse(form.is_valid()) self.assertEqual( @@ -45,7 +45,7 @@ def test_blank_data(self): ) def test_invalid_email(self): - form = CustomUserCreationForm( + form = UserCreationForm( { "username": self.username, "email": "not a valid email", @@ -64,7 +64,7 @@ def test_invalid_email(self): ) def test_passwords_do_not_match(self): - form = CustomUserCreationForm( + form = UserCreationForm( { "username": self.username, "email": self.email, diff --git a/app/accounts/urls.py b/app/accounts/urls.py index eb6d59cb..5d4e0427 100644 --- a/app/accounts/urls.py +++ b/app/accounts/urls.py @@ -5,29 +5,31 @@ urlpatterns = [ path("register/", views.register, name="accounts_register"), - path("login/", auth_views.LoginView.as_view(template_name="accounts/login.html"), name="login"), + path("login/", views.LoginView.as_view(), name="login"), + path("logout/", auth_views.LogoutView.as_view(), name="logout"), + path( + "password_change/", + views.PasswordChangeView.as_view(), + name="password_change", + ), path( "password_reset/", - auth_views.PasswordResetView.as_view(template_name="accounts/password_reset_form.html"), + views.PasswordResetView.as_view(), name="password_reset", ), path( "password_reset/done/", - auth_views.PasswordResetDoneView.as_view(template_name="accounts/password_reset_done.html"), + auth_views.PasswordResetDoneView.as_view(), name="password_reset_done", ), path( "reset///", - auth_views.PasswordResetConfirmView.as_view( - template_name="accounts/password_reset_confirm.html" - ), + views.PasswordResetConfirmView.as_view(), name="password_reset_confirm", ), path( "reset/done/", - auth_views.PasswordResetCompleteView.as_view( - template_name="accounts/password_reset_complete.html" - ), + auth_views.PasswordResetCompleteView.as_view(), name="password_reset_complete", ), ] diff --git a/app/accounts/views.py b/app/accounts/views.py index e02657fa..375e0135 100644 --- a/app/accounts/views.py +++ b/app/accounts/views.py @@ -1,13 +1,25 @@ from django.contrib.auth import authenticate from django.contrib.auth import login as auth_login +from django.contrib.auth.views import LoginView as _LoginView +from django.contrib.auth.views import PasswordChangeView as _PasswordChangeView +from django.contrib.auth.views import ( + PasswordResetConfirmView as _PasswordResetConfirmView, +) +from django.contrib.auth.views import PasswordResetView as _PasswordResetView from django.shortcuts import redirect, render -from .forms import CustomAuthenticationForm, CustomUserCreationForm +from .forms import ( + AuthenticationForm, + PasswordChangeForm, + PasswordResetForm, + SetPasswordForm, + UserCreationForm, +) def register(request): if request.method == "POST": - form = CustomUserCreationForm(request.POST) + form = UserCreationForm(request.POST) if form.is_valid(): user = form.save(commit=False) user.is_staff = True @@ -15,18 +27,29 @@ def register(request): auth_login(request, user) return redirect("home") else: - form = CustomUserCreationForm() - return render(request, "accounts/register.html", {"form": form}) + form = UserCreationForm() + return render(request, "registration/register.html", {"form": form}) -def user_login(request): - if request.method == "POST": - form = CustomAuthenticationForm(request, data=request.POST) - if form.is_valid(): - user = form.get_user() - auth_login(request, user) - return redirect("home") - else: - form = CustomAuthenticationForm() +# We subclass the builtin views where we want to supply our own forms. We +# (mostly) stick to the expected template names, and they are therefore +# automatically picked up, even in views where we don't subclass the builtin +# views. +class LoginView(_LoginView): + form_class = AuthenticationForm + + +class PasswordChangeView(_PasswordChangeView): + form_class = PasswordChangeForm + # The builtin view expects password_change_form.html, but so does the admin + # interface, and we don't want to replace that one as well, so we use a + # custom name. + template_name = "registration/password_change_form2.html" + + +class PasswordResetView(_PasswordResetView): + form_class = PasswordResetForm + - return render(request, "accounts/login.html", {"form": form}) +class PasswordResetConfirmView(_PasswordResetConfirmView): + form_class = SetPasswordForm diff --git a/app/app/urls.py b/app/app/urls.py index d5dbdcbc..8f29d3d5 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -44,7 +44,6 @@ path("search/", views.search, name="search"), path("i18n/", include("django.conf.urls.i18n")), path("accounts/", include("accounts.urls")), - path("accounts/", include("django.contrib.auth.urls")), ] if settings.DEBUG: diff --git a/app/templates/accounts/password_reset_complete.html b/app/templates/accounts.bak/password_reset_complete.html similarity index 100% rename from app/templates/accounts/password_reset_complete.html rename to app/templates/accounts.bak/password_reset_complete.html diff --git a/app/templates/accounts/login.html b/app/templates/accounts/login.html deleted file mode 100644 index 395c6311..00000000 --- a/app/templates/accounts/login.html +++ /dev/null @@ -1,76 +0,0 @@ -{% extends BASE_TEMPLATE %} -{% load static %} -{% load i18n %} - -{% block title %}{% trans "Log In" %}{% endblock %} - -{% block content %} -
-
-
- -
-
-
-{% endblock %} diff --git a/app/templates/accounts/password_reset_confirm.html b/app/templates/accounts/password_reset_confirm.html deleted file mode 100644 index 2711bf34..00000000 --- a/app/templates/accounts/password_reset_confirm.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends BASE_TEMPLATE %} -{% load static %} -{% load i18n %} - -{% block title %}{% trans "Enter new password" %}{% endblock %} - -{% block content %} - - {% if validlink %} - -
-
- -
-
- -{% endblock %} diff --git a/app/templates/accounts/password_reset_done.html b/app/templates/accounts/password_reset_done.html deleted file mode 100644 index 2f344f51..00000000 --- a/app/templates/accounts/password_reset_done.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends BASE_TEMPLATE %} -{% load static %} -{% load i18n %} - -{% block title %}Email Sent{% endblock %} - -{% block content %} -
-
- -
-
-{% endblock %} diff --git a/app/templates/accounts/password_reset_form.html b/app/templates/accounts/password_reset_form.html deleted file mode 100644 index a23fafcf..00000000 --- a/app/templates/accounts/password_reset_form.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block title %}{% trans "Forgot Your Password?" %}{% endblock %} - -{% block content %} -
-
- -
-
-{% endblock %} diff --git a/app/templates/accounts/register.html b/app/templates/accounts/register.html deleted file mode 100644 index 49839522..00000000 --- a/app/templates/accounts/register.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends BASE_TEMPLATE %} -{% load static %} -{% load i18n %} - -{% block title %}{% trans "Sign Up" %}{% endblock %} - -{% block content %} -
-
-
- -
-
-
-{% endblock %} diff --git a/app/templates/registration/login.html b/app/templates/registration/login.html new file mode 100644 index 00000000..10968cca --- /dev/null +++ b/app/templates/registration/login.html @@ -0,0 +1,20 @@ +{% extends "registration/registration_base.html" %} +{% load i18n %} + +{% block title %}{% trans "Log In" %}{% endblock %} + +{% block account_content %} +

{% trans "Log In" %}

+
+ {% csrf_token %} + {{ form }} + {% trans "Forgot Password?" %} + +
+ +
+

+ {% trans "Don’t have an account?" %} + {% trans "Create one" %} +

+{% endblock %} diff --git a/app/templates/registration/password_change_done.html b/app/templates/registration/password_change_done.html new file mode 100644 index 00000000..24d2f913 --- /dev/null +++ b/app/templates/registration/password_change_done.html @@ -0,0 +1,9 @@ +{% extends "registration/registration_base.html" %} +{% load i18n %} + +{% block title %}{% trans "Password change successful" %}{% endblock %} + +{% block account_content %} +

{% trans "Password change successful" %}

+

{% trans "Your password was changed." %} +{% endblock %} diff --git a/app/templates/registration/password_change_form2.html b/app/templates/registration/password_change_form2.html new file mode 100644 index 00000000..762359ed --- /dev/null +++ b/app/templates/registration/password_change_form2.html @@ -0,0 +1,19 @@ +{% extends "registration/registration_base.html" %} +{% load i18n %} + +{% block title %}{% trans "Enter new password" %}{% endblock %} + +{% block account_content %} +

{% trans "Enter new password" %}

+

+ {% blocktrans trimmed %} + Please enter your old password, for security’s sake, and then enter your new + password twice so we can verify you typed it in correctly. + {% endblocktrans %} +

+
+ {% csrf_token %} + {{ form }} + +
+{% endblock %} diff --git a/app/templates/registration/password_reset_complete.html b/app/templates/registration/password_reset_complete.html new file mode 100644 index 00000000..2dc8c0d7 --- /dev/null +++ b/app/templates/registration/password_reset_complete.html @@ -0,0 +1,12 @@ +{% extends "registration/registration_base.html" %} +{% load i18n %} + +{% block title %}{% trans "Password reset complete" %}{% endblock %} + +{% block account_content %} +

{% trans "Password was successfully set" %}

+

{% trans "Your password has been set. You may go ahead and log in now." %}

+

+ {% trans "Log in" %} +

+{% endblock %} diff --git a/app/templates/registration/password_reset_confirm.html b/app/templates/registration/password_reset_confirm.html new file mode 100644 index 00000000..4ab6510f --- /dev/null +++ b/app/templates/registration/password_reset_confirm.html @@ -0,0 +1,26 @@ +{% extends "registration/registration_base.html" %} +{% load i18n %} + +{% block title %}{% trans "Enter new password" %}{% endblock %} + +{% block account_content %} + {% if validlink %} +

{% trans "Enter new password" %}

+

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

+ +
{% csrf_token %} + {{ form }} + +
+ + {% else %} + +

{% trans "Password reset link was invalid" %}

+

+ {% blocktrans trimmed %} + The password reset link was invalid, possibly because it has + already been used. Please request a new password reset. + {% endblocktrans %} +

+ {% endif %} +{% endblock %} diff --git a/app/templates/registration/password_reset_done.html b/app/templates/registration/password_reset_done.html new file mode 100644 index 00000000..390d970d --- /dev/null +++ b/app/templates/registration/password_reset_done.html @@ -0,0 +1,20 @@ +{% extends "registration/registration_base.html" %} +{% load i18n %} + +{% block title %}Password reset sent{% endblock %} + +{% block account_content %} +

{% trans "Password reset sent" %}

+

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

+

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

+{% endblock %} diff --git a/app/templates/registration/password_reset_form.html b/app/templates/registration/password_reset_form.html new file mode 100644 index 00000000..29277340 --- /dev/null +++ b/app/templates/registration/password_reset_form.html @@ -0,0 +1,19 @@ +{% extends "registration/registration_base.html" %} +{% load i18n %} + +{% block title %}{% trans "Password reset" %}{% endblock %} + +{% block account_content %} +

{% trans "Password reset" %}

+

+ {% blocktrans trimmed %} + Forgotten your password? Enter your email address below, and we’ll email + instructions for setting a new one. + {% endblocktrans %} +

+
+ {% csrf_token %} + {{ form }} + +
+{% endblock %} diff --git a/app/templates/registration/register.html b/app/templates/registration/register.html new file mode 100644 index 00000000..79880100 --- /dev/null +++ b/app/templates/registration/register.html @@ -0,0 +1,13 @@ +{% extends "registration/registration_base.html" %} +{% load i18n %} + +{% block title %}{% trans "Register" %}{% endblock %} + +{% block account_content %} +

{% trans "Register" %}

+
+ {% csrf_token %} + {{ form }} + +
+{% endblock %} diff --git a/app/templates/registration/registration_base.html b/app/templates/registration/registration_base.html new file mode 100644 index 00000000..d72902b7 --- /dev/null +++ b/app/templates/registration/registration_base.html @@ -0,0 +1,14 @@ +{% extends BASE_TEMPLATE %} + +{% spaceless %} +{% block content %} +
+
+
+ {% block account_content%} + {% endblock %} +
+
+
+{% endblock %} +{% endspaceless %}