diff --git a/core/templates/core/login.jinja b/core/templates/core/login.jinja index 65a1f8d5..a6c3332d 100644 --- a/core/templates/core/login.jinja +++ b/core/templates/core/login.jinja @@ -26,9 +26,11 @@ {% endif %} {% endif %} -
+ {% if form.errors %} -

{% trans %}Your username and password didn't match. Please try again.{% endtrans %}

+

+ {% trans %}Your credentials didn't match. Please try again.{% endtrans %} +

{% endif %} {% csrf_token %} diff --git a/core/tests/test_core.py b/core/tests/test_core.py index 72cde11c..a5320a5d 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -38,6 +38,7 @@ from core.markdown import markdown from core.models import AnonymousUser, Group, Page, User from core.utils import get_semester_code, get_start_of_semester from core.views import AllowFragment +from counter.models import Customer from sith import settings @@ -151,24 +152,44 @@ class TestUserLogin: def user(self) -> User: return baker.make(User, password=make_password("plop")) - def test_login_fail(self, client, user): + @pytest.mark.parametrize( + "identifier_getter", + [ + lambda user: user.username, + lambda user: user.email, + lambda user: Customer.get_or_create(user)[0].account_id, + ], + ) + def test_login_fail(self, client, user, identifier_getter): """Should not login a user correctly.""" + identifier = identifier_getter(user) response = client.post( reverse("core:login"), - {"username": user.username, "password": "wrong-password"}, + {"username": identifier, "password": "wrong-password"}, ) assert response.status_code == 200 - assert ( - '

Votre nom d\'utilisateur ' - "et votre mot de passe ne correspondent pas. Merci de réessayer.

" - ) in response.text assert response.wsgi_request.user.is_anonymous + soup = BeautifulSoup(response.text, "lxml") + form = soup.find(id="login-form") + assert ( + form.find(class_="alert alert-red").get_text(strip=True) + == "Vos identifiants ne correspondent pas. Veuillez réessayer." + ) + assert form.find("input", attrs={"name": "username"}).get("value") == identifier - def test_login_success(self, client, user): + @pytest.mark.parametrize( + "identifier_getter", + [ + lambda user: user.username, + lambda user: user.email, + lambda user: Customer.get_or_create(user)[0].account_id, + ], + ) + def test_login_success(self, client, user, identifier_getter): """Should login a user correctly.""" response = client.post( reverse("core:login"), - {"username": user.username, "password": "plop"}, + {"username": identifier_getter(user), "password": "plop"}, ) assertRedirects(response, reverse("core:index")) assert response.wsgi_request.user == user diff --git a/core/views/forms.py b/core/views/forms.py index 02f2ae26..a8bbdfd6 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -132,29 +132,31 @@ class FutureDateTimeField(forms.DateTimeField): class LoginForm(AuthenticationForm): def __init__(self, *arg, **kwargs): - if "data" in kwargs: - from counter.models import Customer - - data = kwargs["data"].copy() - account_code = re.compile(r"^[0-9]+[A-Za-z]$") - try: - if account_code.match(data["username"]): - user = ( - Customer.objects.filter(account_id__iexact=data["username"]) - .first() - .user - ) - elif "@" in data["username"]: - user = User.objects.filter(email__iexact=data["username"]).first() - else: - user = User.objects.filter(username=data["username"]).first() - data["username"] = user.username - except: # noqa E722 I don't know what error is supposed to be raised here - pass - kwargs["data"] = data super().__init__(*arg, **kwargs) self.fields["username"].label = _("Username, email, or account number") + def clean_username(self): + identifier: str = self.cleaned_data["username"] + account_code = re.compile(r"^[0-9]+[A-Za-z]$") + if account_code.match(identifier): + qs_filter = "customer__account_id__iexact" + elif identifier.count("@") == 1: + qs_filter = "email" + else: + qs_filter = None + if qs_filter: + # if the user gave an email or an account code instead of + # a username, retrieve and return the corresponding username. + # If there is no username, return an empty string, so that + # Django will properly handle the error when failing the authentication + identifier = ( + User.objects.filter(**{qs_filter: identifier}) + .values_list("username", flat=True) + .first() + or "" + ) + return identifier + class RegisteringForm(UserCreationForm): error_css_class = "error" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 01b3f706..d6549184 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-16 14:54+0200\n" +"POT-Creation-Date: 2025-06-25 16:29+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -2015,10 +2015,8 @@ msgid "Please login or create an account to see this page." msgstr "Merci de vous identifier ou de créer un compte pour voir cette page." #: core/templates/core/login.jinja -msgid "Your username and password didn't match. Please try again." -msgstr "" -"Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Merci de " -"réessayer." +msgid "Your credentials didn't match. Please try again." +msgstr "Vos identifiants ne correspondent pas. Veuillez réessayer." #: core/templates/core/login.jinja msgid "Lost password?"