From 9b1483f791a7b1a05a0cfa4aca803fa3258e9883 Mon Sep 17 00:00:00 2001 From: "Aurore \"Lugrim\" Poirier" Date: Sun, 8 Oct 2023 00:32:08 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=8C=90=20Translated=20some=20missing?= =?UTF-8?q?=20strings=20in=20user=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- insalan/user/models.py | 12 +- insalan/user/serializers.py | 2 +- insalan/user/views.py | 2 +- locale/en/LC_MESSAGES/django.po | 214 ++++++++++++++++++++++---------- 4 files changed, 157 insertions(+), 73 deletions(-) diff --git a/insalan/user/models.py b/insalan/user/models.py index 32f70f5f..7c8bbb8a 100644 --- a/insalan/user/models.py +++ b/insalan/user/models.py @@ -31,11 +31,11 @@ def create_user( check that all required fields are present and create an user """ if not email: - raise ValueError(_("An email is required")) + raise ValueError(_("Un courriel est requis")) if not username: - raise ValueError(_("An username is required")) + raise ValueError(_("Un nom d'utilisateur·rice est requis")) if not password: - raise ValueError(_("A password is required")) + raise ValueError(_("Un mot de passe est requis")) user = self.model( email=self.normalize_email(email), username=username, @@ -48,7 +48,7 @@ def create_user( def create_superuser(self, email, username, password, **extra_fields): if password is None: - raise TypeError("Superusers must have a password.") + raise TypeError(_("Les superutilisateur·rices requièrent un mot de passe")) user = self.create_user(email, username, password, **extra_fields) user.is_superuser = True user.is_staff = True @@ -122,8 +122,8 @@ def send_email_confirmation(user_object: User): user = user_object.username # TODO Give a frontend page instead of direct API link send_mail( - _("Confirmez votre e-mail"), - _("Confirmez votre adresse e-mail en cliquant sur ") + _("Confirmez votre courriel"), + _("Confirmez votre adresse de courriel en cliquant sur ") + "http://api." + getenv("WEBSITE_HOST", "localhost") + reverse("confirm-email", kwargs={"user": user, "token": token}), diff --git a/insalan/user/serializers.py b/insalan/user/serializers.py index e8b8947f..c5e8a2fe 100644 --- a/insalan/user/serializers.py +++ b/insalan/user/serializers.py @@ -90,7 +90,7 @@ def check_validity(self, data): user = authenticate(username=data["username"], password=data["password"]) if user is not None: if not user.is_active or not user.email_active: - raise serializers.ValidationError(_("Account not actived")) + raise serializers.ValidationError(_("Compte non activé")) return user diff --git a/insalan/user/views.py b/insalan/user/views.py index 21a8a833..a93c13c7 100644 --- a/insalan/user/views.py +++ b/insalan/user/views.py @@ -161,7 +161,7 @@ def post(self, request): except User.DoesNotExist: return Response( - {"user": [_("Utilisateur non trouvé")]}, + {"user": [_("Utilisateur⋅ice non trouvé⋅e")]}, status=status.HTTP_400_BAD_REQUEST, ) diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 36fa8736..02a8b7e1 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-08-30 01:19+0200\n" +"POT-Creation-Date: 2023-10-07 22:28+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Amélie Gonzalez \n" +"Last-Translator: Aurore Poirier \n" "Language-Team: SysRez-Dev \n" "Language: English\n" "MIME-Version: 1.0\n" @@ -18,243 +18,327 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: langate/tests.py:232 langate/views.py:67 +#: insalan/langate/tests.py:236 insalan/langate/views.py:67 msgid "Évènement demandé incompatible" msgstr "Mismatched requested event" -#: langate/tests.py:258 langate/views.py:74 +#: insalan/langate/tests.py:262 insalan/langate/views.py:74 msgid "Identifiant manquant" msgstr "Missing identifier" -#: langate/tests.py:283 langate/views.py:79 langate/views.py:87 +#: insalan/langate/tests.py:287 insalan/langate/views.py:79 +#: insalan/langate/views.py:87 msgid "Évènement non en cours" msgstr "Event not ongoing" -#: langate/views.py:54 +#: insalan/langate/views.py:54 msgid "Pas d'évènement en cours" msgstr "No ongoing event" -#: partner/apps.py:8 +#: insalan/partner/apps.py:8 msgid "Partenaires & Sponsors" msgstr "Partners & Sponsors" -#: partner/models.py:14 partner/models.py:19 +#: insalan/partner/models.py:14 insalan/partner/models.py:20 msgid "Partenaire" msgstr "Partner" -#: partner/models.py:15 +#: insalan/partner/models.py:15 msgid "Sponsor" msgstr "Sponsor" -#: partner/models.py:20 +#: insalan/partner/models.py:21 msgid "Partenaires" msgstr "Partners" -#: partner/models.py:25 +#: insalan/partner/models.py:25 msgid "Nom du partenaire/sponsor" msgstr "Partner/Sponsor name" -#: partner/models.py:27 +#: insalan/partner/models.py:27 msgid "URL" msgstr "URL" -#: partner/models.py:29 tournament/models.py:60 tournament/models.py:151 +#: insalan/partner/models.py:29 insalan/tournament/models.py:60 +#: insalan/tournament/models.py:156 msgid "Logo" msgstr "Logo" -#: partner/models.py:35 +#: insalan/partner/models.py:36 msgid "Type de partenariat" msgstr "Partnership Type" -#: settings.py:139 +#: insalan/settings.py:143 msgid "Anglais" msgstr "English" -#: settings.py:140 +#: insalan/settings.py:144 msgid "Français" msgstr "French" -#: settings.py:141 +#: insalan/settings.py:145 msgid "Espagnol" msgstr "Spanish" -#: settings.py:142 +#: insalan/settings.py:146 msgid "Allemand" msgstr "German" -#: tickets/apps.py:8 tickets/models.py:20 +#: insalan/tickets/apps.py:8 insalan/tickets/models.py:19 msgid "Tickets" msgstr "Tickets" -#: tickets/models.py:13 +#: insalan/tickets/models.py:11 msgid "Annulé" msgstr "Canceled" -#: tickets/models.py:14 +#: insalan/tickets/models.py:12 msgid "Scanné" msgstr "Scanned" -#: tickets/models.py:15 +#: insalan/tickets/models.py:13 msgid "Valide" msgstr "Valid" -#: tickets/models.py:19 +#: insalan/tickets/models.py:18 msgid "Ticket" msgstr "Ticket" -#: tickets/models.py:23 +#: insalan/tickets/models.py:22 msgid "UUID" msgstr "UUID" -#: tickets/models.py:29 tournament/models.py:285 tournament/models.py:345 -#: user/models.py:32 +#: insalan/tickets/models.py:25 insalan/tournament/models.py:293 +#: insalan/tournament/models.py:354 insalan/user/models.py:73 msgid "Utilisateur⋅ice" msgstr "User" -#: tickets/models.py:32 +#: insalan/tickets/models.py:28 msgid "Statut" msgstr "Status" -#: tickets/views.py:26 tickets/views.py:55 tickets/views.py:83 +#: insalan/tickets/tests.py:85 insalan/tickets/tests.py:153 +#: insalan/tickets/tests.py:193 insalan/tickets/views.py:26 +#: insalan/tickets/views.py:57 insalan/tickets/views.py:85 msgid "UUID invalide" msgstr "Invalid UUID" -#: tickets/views.py:32 +#: insalan/tickets/tests.py:93 insalan/tickets/views.py:32 +#: insalan/user/views.py:164 msgid "Utilisateur⋅ice non trouvé⋅e" msgstr "User not found" -#: tickets/views.py:38 tickets/views.py:61 tickets/views.py:89 +#: insalan/tickets/tests.py:101 insalan/tickets/tests.py:159 +#: insalan/tickets/tests.py:187 insalan/tickets/tests.py:199 +#: insalan/tickets/views.py:38 insalan/tickets/views.py:63 +#: insalan/tickets/views.py:91 msgid "Ticket non trouvé" msgstr "Ticket not found" -#: tickets/views.py:65 +#: insalan/tickets/tests.py:165 insalan/tickets/views.py:67 msgid "Ticket annulé" msgstr "Ticket canceled" -#: tickets/views.py:68 +#: insalan/tickets/tests.py:171 insalan/tickets/views.py:70 msgid "Ticket déjà scanné" msgstr "Ticket already scanned" -#: tournament/apps.py:10 tournament/models.py:162 +#: insalan/tournament/apps.py:11 insalan/tournament/models.py:168 msgid "Tournois" msgstr "Tournaments" -#: tournament/models.py:37 +#: insalan/tournament/models.py:37 msgid "Nom de l'évènement" msgstr "Event name" -#: tournament/models.py:43 +#: insalan/tournament/models.py:43 msgid "Description de l'évènement" msgstr "Event description" -#: tournament/models.py:46 +#: insalan/tournament/models.py:46 msgid "Année" msgstr "Year" -#: tournament/models.py:51 +#: insalan/tournament/models.py:51 msgid "Mois" msgstr "Month" -#: tournament/models.py:56 +#: insalan/tournament/models.py:56 msgid "En cours" msgstr "Ongoing" -#: tournament/models.py:70 tournament/models.py:134 +#: insalan/tournament/models.py:71 insalan/tournament/models.py:135 msgid "Évènement" msgstr "Event" -#: tournament/models.py:71 +#: insalan/tournament/models.py:72 msgid "Évènements" msgstr "Events" -#: tournament/models.py:97 tournament/models.py:139 +#: insalan/tournament/models.py:98 insalan/tournament/models.py:140 msgid "Jeu" msgstr "Game" -#: tournament/models.py:98 +#: insalan/tournament/models.py:99 msgid "Jeux" msgstr "Games" -#: tournament/models.py:101 +#: insalan/tournament/models.py:102 msgid "Nom du jeux" msgstr "Game name" -#: tournament/models.py:107 +#: insalan/tournament/models.py:108 msgid "Nom raccourci du jeu" msgstr "Short game name" -#: tournament/models.py:143 +#: insalan/tournament/models.py:144 msgid "Nom du tournoi" msgstr "Tournament name" -#: tournament/models.py:147 +#: insalan/tournament/models.py:149 msgid "Règlement du tournoi" msgstr "Tournament rules" -#: tournament/models.py:161 tournament/models.py:204 +#: insalan/tournament/models.py:167 insalan/tournament/models.py:210 msgid "Tournoi" msgstr "Tournament" -#: tournament/models.py:210 +#: insalan/tournament/models.py:216 msgid "Nom d'équipe" msgstr "Team name" -#: tournament/models.py:215 tournament/models.py:290 tournament/models.py:349 +#: insalan/tournament/models.py:221 insalan/tournament/models.py:298 +#: insalan/tournament/models.py:358 msgid "Équipe" msgstr "Team" -#: tournament/models.py:216 +#: insalan/tournament/models.py:222 msgid "Équipes" msgstr "Teams" -#: tournament/models.py:267 +#: insalan/tournament/models.py:273 msgid "Pas payé" msgstr "Not paid" -#: tournament/models.py:268 +#: insalan/tournament/models.py:274 msgid "Payé" msgstr "Paid" -#: tournament/models.py:269 +#: insalan/tournament/models.py:275 msgid "Payera sur place" msgstr "Will pay on site" -#: tournament/models.py:279 +#: insalan/tournament/models.py:287 msgid "Inscription d'un⋅e joueur⋅euse" msgstr "Player Registration" -#: tournament/models.py:280 +#: insalan/tournament/models.py:288 msgid "Inscription de joueur⋅euses" msgstr "Player Registrations" -#: tournament/models.py:298 tournament/models.py:352 +#: insalan/tournament/models.py:306 insalan/tournament/models.py:361 msgid "Statut du paiement" msgstr "Payment Status" -#: tournament/models.py:337 +#: insalan/tournament/models.py:345 msgid "Joueur⋅euse déjà inscrit⋅e pour cet évènement" msgstr "Player already registered for that event" -#: tournament/models.py:362 +#: insalan/tournament/models.py:372 msgid "Inscription d'un⋅e manager" msgstr "Manager Registration" -#: tournament/models.py:363 +#: insalan/tournament/models.py:373 msgid "Inscriptions de managers" msgstr "Manager Registrations" -#: user/apps.py:7 +#: insalan/user/apps.py:13 msgid "Gestionnaire d'utilisateur⋅ices" msgstr "User Management Application" -#: user/models.py:26 +#: insalan/user/models.py:34 +msgid "Un courriel est requis" +msgstr "An email is required" + +#: insalan/user/models.py:36 +msgid "Un nom d'utilisateur·rice est requis" +msgstr "An username is required" + +#: insalan/user/models.py:38 +msgid "Un mot de passe est requis" +msgstr "A password is required" + +#: insalan/user/models.py:52 +msgid "Les superutilisateur·rices requièrent un mot de passe" +msgstr "Superusers require a password" + +#: insalan/user/models.py:74 +msgid "Utilisateur⋅ices" +msgstr "Users" + +#: insalan/user/models.py:83 msgid "Courriel" msgstr "Email" -#: user/models.py:28 +#: insalan/user/models.py:86 msgid "Courriel vérifié" msgstr "Email activated" -#: user/models.py:33 -msgid "Utilisateur⋅ices" -msgstr "Users" +#: insalan/user/models.py:127 +msgid "Confirmez votre courriel" +msgstr "Confirm your email" + +#: insalan/user/models.py:128 +msgid "Confirmez votre adresse de courriel en cliquant sur " +msgstr "Confirm your email by clicking on " + +#: insalan/user/models.py:145 +msgid "Demande de ré-initialisation de mot de passe" +msgstr "Ask for a password reset" + +#: insalan/user/models.py:147 +msgid "" +"Une demande de ré-initialisation de mot de passe a été effectuéepour votre " +"compte. Si vous êtes à l'origine de cette demande,vous pouvez cliquer sur le " +"lien suivant: " +msgstr "" + +#: insalan/user/serializers.py:63 insalan/user/views.py:153 +msgid "Les mots de passe diffèrent" +msgstr "Passwords are different" + +#: insalan/user/serializers.py:93 +msgid "Compte non activé" +msgstr "Account not activated" + +#: insalan/user/tests.py:301 insalan/user/views.py:233 +msgid "Nom d'utilisateur ou mot de passe incorrect" +msgstr "Username or password incorrect" + +#: insalan/user/views.py:33 +msgid "Le cookie a été défini" +msgstr "Cookie has been defined" + +#: insalan/user/views.py:79 +msgid "Utilisateur ou jeton invalide (ou adresse déjà confirmée)" +msgstr "User or token invalid, or account is already confirmed" + +#: insalan/user/views.py:129 +msgid "Champ manquant" +msgstr "Missing field" + +#: insalan/user/views.py:146 +msgid "Mot de passe trop simple ou invalide" +msgstr "Password too simple or invalid" + +#: insalan/user/views.py:158 +msgid "Jeton de ré-initialisation invalide" +msgstr "Reset token invalid" + +#: insalan/user/views.py:180 +msgid "Impossible de renvoyer le courriel de confirmation" +msgstr "Impossible to send confirmation email again" + +#: insalan/user/views.py:239 +msgid "Format des données soumises invalide" +msgstr "Submitted data format invalid" From 754c8840126ee33195971d58b6517386f2b28c01 Mon Sep 17 00:00:00 2001 From: "Aurore \"Lugrim\" Poirier" Date: Sun, 8 Oct 2023 01:42:24 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=9A=A8=20Linting=20score=20is=20getti?= =?UTF-8?q?ng=20dangerously=20close=209?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed a few warnings, mostly by adding docstrings --- insalan/user/models.py | 4 ++ insalan/user/tests.py | 85 ++++++++++++++++++++++++++++++++++++++++++ insalan/user/views.py | 38 ++++++++++++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/insalan/user/models.py b/insalan/user/models.py index 7c8bbb8a..984725a2 100644 --- a/insalan/user/models.py +++ b/insalan/user/models.py @@ -47,6 +47,9 @@ def create_user( return user def create_superuser(self, email, username, password, **extra_fields): + """ + Check that all required fields are present and create a superuser + """ if password is None: raise TypeError(_("Les superutilisateur·rices requièrent un mot de passe")) user = self.create_user(email, username, password, **extra_fields) @@ -132,6 +135,7 @@ def send_email_confirmation(user_object: User): fail_silently=False, ) + @staticmethod def send_password_reset(user_object: User): """ Send a password reset token. diff --git a/insalan/user/tests.py b/insalan/user/tests.py index a814afc0..24e4cd21 100644 --- a/insalan/user/tests.py +++ b/insalan/user/tests.py @@ -10,7 +10,14 @@ class UserTestCase(TestCase): + """ + Tests of the User model + """ + def setUp(self): + """ + Create some base users to do operations on + """ u: User = User.objects.create_user( username="staffplayer", email="staff@insalan.fr", @@ -43,6 +50,9 @@ def setUp(self): ) def test_get_existing_full_user(self): + """ + Test getting all the fields of an already created user + """ u: User = User.objects.get(username="randomplayer") self.assertEquals(u.get_username(), "randomplayer") self.assertEquals(u.get_short_name(), "Random") @@ -54,6 +64,9 @@ def test_get_existing_full_user(self): self.assertFalse(u.is_staff) def test_get_existing_minimal_user(self): + """ + Test getting all the fields of an user created with only the required fields + """ u: User = User.objects.get(username="anotherplayer") self.assertEquals(u.get_username(), "anotherplayer") self.assertEquals(u.get_short_name(), "") @@ -65,14 +78,24 @@ def test_get_existing_minimal_user(self): self.assertFalse(u.is_staff) def test_get_non_existing_user(self): + """ + Test that getting an user which does not exist throws an `User.DoesNotExist` error + """ with self.assertRaises(User.DoesNotExist): User.objects.get(username="idontexist") class UserEndToEndTestCase(TestCase): + """ + Test cases of the API endpoints and workflows related to user model + """ + client: APIClient def setUp(self): + """ + Create a player to test getters + """ self.client = APIClient() User.objects.create_user( username="randomplayer", @@ -85,6 +108,10 @@ def setUp(self): ) def test_register_invalid_data(self): + """ + Test trying to register a few invalid users + """ + def send_invalid_data(data): request = self.client.post("/v1/user/register/", data, format="json") self.assertEquals(request.status_code, 400) @@ -95,7 +122,14 @@ def send_invalid_data(data): send_invalid_data({"username": "newuser", "email": "email@example.com"}) def test_register_valid_account(self): + """ + Test registering valid users + """ + def send_valid_data(data, check_fields=[]): + """ + Helper function that will request a register and check its output + """ request = self.client.post("/v1/user/register/", data, format="json") self.assertEquals(request.status_code, 201) @@ -144,6 +178,10 @@ def send_valid_data(data, check_fields=[]): ) def test_register_read_only_fields(self): + """ + Test that the read-only register fields are indeed read-only + """ + def send_valid_data(data, check_fields=[]): request = self.client.post("/v1/user/register/", data, format="json") @@ -197,6 +235,9 @@ def send_valid_data(data, check_fields=[]): ) def test_register_email_is_sent(self): + """ + Test that an email is sent upon registration + """ data = { "username": "ILoveMail", "password": "1111qwer!", @@ -212,6 +253,9 @@ def test_register_email_is_sent(self): self.assertEqual(mail.outbox[0].to, ["postman@example.com"]) def test_register_can_confirm_email(self): + """ + Test that an user can confirm their email with the link they received + """ data = { "username": "ILoveEmail", "password": "1111qwer!", @@ -241,6 +285,9 @@ def test_register_can_confirm_email(self): self.assertTrue(User.objects.get(username=data["username"]).email_active) def test_can_confirm_email_only_once(self): + """ + Test that an user can confirm their email only once + """ data = { "username": "ILoveEmail", "password": "1111qwer!", @@ -266,6 +313,9 @@ def test_can_confirm_email_only_once(self): self.assertEquals(request.status_code, 400) def test_confirmation_email_is_token_checked(self): + """ + Test that the token sent to an user for their email confirmation is really checked + """ data = { "username": "ILoveEmail", "password": "1111qwer!", @@ -292,6 +342,10 @@ def test_confirmation_email_is_token_checked(self): self.assertEquals(request.status_code, 400) def test_login_invalid_account(self): + """ + Try to login with invalid requests + """ + def send_valid_data(data): request = self.client.post("/v1/user/login/", data, format="json") @@ -309,6 +363,9 @@ def send_valid_data(data): ) def test_login_not_active_account(self): + """ + Test trying to login to an account which email is not already activated + """ User.objects.create_user( username="newplayer", email="test@test.com", password="1111qwer!" ) @@ -326,6 +383,9 @@ def send_valid_data(data): ) def test_login_account(self): + """ + Test that when everything is ok, an user is able to login + """ User.objects.create_user( username="newplayer", email="test@test.com", @@ -347,6 +407,10 @@ def send_valid_data(data): ) def can_resend_confirmation_email(self): + """ + Test that an user can request another confirmation email when + requesting the right route + """ data = { "username": "ILoveEmail", "password": "1111qwer!", @@ -373,6 +437,10 @@ def can_resend_confirmation_email(self): self.assertEqual(mail.outbox, 3) def cant_resend_confirmation_if_already_valid(self): + """ + Test that an user cannot resend a confirmation email if they already + confirmed their email + """ User.objects.create_user( username="newplayer", email="test@example.com", @@ -387,6 +455,10 @@ def cant_resend_confirmation_if_already_valid(self): self.assertEqual(request.status_code, 400) def cant_resend_confirmation_if_nonexisting_user(self): + """ + Test that we cannot resend a confirmation email for a non existing user + without crashing the server + """ request = self.client.post( "/v1/user/resend-email/", {"username": "IDontExistLol"}, format="json" ) @@ -394,11 +466,18 @@ def cant_resend_confirmation_if_nonexisting_user(self): self.assertEqual(request.status_code, 400) def dont_crash_resend_confirmation_if_empty(self): + """ + Test that server doesn't crash if ask to resend an email without any + valid data in request + """ request = self.client.post("/v1/user/resend-email/", {}, format="json") self.assertEqual(request.status_code, 400) def test_can_reset_password(self): + """ + Test that an user can reset their password (full workflow) + """ data = { "email": "randomplayer@example.com", } @@ -446,6 +525,9 @@ def test_can_reset_password(self): self.assertEquals(request.status_code, 404) def test_can_reset_password_only_once(self): + """ + Test that an user can reset their password only once with a token + """ data = { "email": "randomplayer@example.com", } @@ -486,6 +568,9 @@ def test_can_reset_password_only_once(self): self.assertEquals(request.status_code, 400) def test_password_reset_is_token_checked(self): + """ + Test that the password reset token is actually checked + """ data = { "email": "randomplayer@example.com", } diff --git a/insalan/user/views.py b/insalan/user/views.py index a93c13c7..0c0b60c8 100644 --- a/insalan/user/views.py +++ b/insalan/user/views.py @@ -60,22 +60,37 @@ def get(self, request): # TODO: change permission class PermissionViewSet(generics.ListCreateAPIView): + """ + Django's `Permission` ViewSet to be able to add them to the admin panel + """ + queryset = Permission.objects.all().order_by("name") serializer_class = PermissionSerializer permission_classes = [permissions.IsAdminUser] class GroupViewSet(generics.ListCreateAPIView): + """ + Django's `Group` ViewSet to be able to add them to the admin panel + """ + queryset = Group.objects.all().order_by("name") serializer_class = GroupSerializer permission_classes = [permissions.IsAdminUser] class EmailConfirmView(APIView): + """ + Email confirmation user API Endpoint + """ + permissions_classes = [permissions.AllowAny] authentication_classes = [SessionAuthentication] def get(self, request, user=None, token=None): + """ + If requested with valid parameters, will validate an user's email + """ error_text = _("Utilisateur ou jeton invalide (ou adresse déjà confirmée)") if user and token: @@ -100,10 +115,18 @@ def get(self, request, user=None, token=None): class AskForPasswordReset(APIView): + """ + Asking for a password reset API Endpoint + """ + permissions_classes = [permissions.AllowAny] authentication_classes = [SessionAuthentication] def post(self, request): + """ + If requested with valid parameters, will send a password reset email to + an user given their email address + """ try: user_object: User = User.objects.get(email=request.data["email"]) UserMailer.send_password_reset(user_object) @@ -114,10 +137,17 @@ def post(self, request): class ResetPassword(APIView): + """ + Password Reset API Endpoint + """ + permissions_classes = [permissions.AllowAny] authentication_classes = [SessionAuthentication] def post(self, request): + """ + If requested with valid parameters, will reset an user password + """ data = request.data if not ( "user" in data @@ -170,13 +200,16 @@ def post(self, request): class ResendEmailConfirmView(APIView): """ - API endpoint to re-send + API endpoint to re-send a confirmation email """ permissions_classes = [permissions.AllowAny] authentication_classes = [SessionAuthentication] def post(self, request): + """ + If the user is found, will send again a confirmation email + """ error_text = _("Impossible de renvoyer le courriel de confirmation") username = request.data.get("username") @@ -250,5 +283,8 @@ class UserLogout(APIView): authentication_classes = [] def post(self, request): + """ + Will logout an user. + """ logout(request) return Response(status=status.HTTP_200_OK) From b2c8f696d612443183b4458b51380889ed42510c Mon Sep 17 00:00:00 2001 From: "Aurore \"Lugrim\" Poirier" Date: Sun, 8 Oct 2023 15:12:35 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=9A=A8=20changed=20a=20few=20deprecat?= =?UTF-8?q?ions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- insalan/user/tests.py | 54 +++++++++++++++++++++---------------------- insalan/user/views.py | 30 ++++++++++++++---------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/insalan/user/tests.py b/insalan/user/tests.py index 24e4cd21..8a322771 100644 --- a/insalan/user/tests.py +++ b/insalan/user/tests.py @@ -54,10 +54,10 @@ def test_get_existing_full_user(self): Test getting all the fields of an already created user """ u: User = User.objects.get(username="randomplayer") - self.assertEquals(u.get_username(), "randomplayer") - self.assertEquals(u.get_short_name(), "Random") - self.assertEquals(u.get_full_name(), "Random Player") - self.assertEquals(u.get_user_permissions(), set()) + self.assertEqual(u.get_username(), "randomplayer") + self.assertEqual(u.get_short_name(), "Random") + self.assertEqual(u.get_full_name(), "Random Player") + self.assertEqual(u.get_user_permissions(), set()) self.assertTrue(u.has_usable_password()) self.assertTrue(u.check_password("IUseAVerySecurePassword")) self.assertTrue(u.is_active) @@ -68,10 +68,10 @@ def test_get_existing_minimal_user(self): Test getting all the fields of an user created with only the required fields """ u: User = User.objects.get(username="anotherplayer") - self.assertEquals(u.get_username(), "anotherplayer") - self.assertEquals(u.get_short_name(), "") - self.assertEquals(u.get_full_name(), "") - self.assertEquals(u.get_user_permissions(), set()) + self.assertEqual(u.get_username(), "anotherplayer") + self.assertEqual(u.get_short_name(), "") + self.assertEqual(u.get_full_name(), "") + self.assertEqual(u.get_user_permissions(), set()) self.assertTrue(u.has_usable_password()) self.assertTrue(u.check_password("ThisIsPassword")) self.assertTrue(u.is_active) @@ -114,7 +114,7 @@ def test_register_invalid_data(self): def send_invalid_data(data): request = self.client.post("/v1/user/register/", data, format="json") - self.assertEquals(request.status_code, 400) + self.assertEqual(request.status_code, 400) send_invalid_data({}) send_invalid_data({"username": "newuser"}) @@ -132,11 +132,11 @@ def send_valid_data(data, check_fields=[]): """ request = self.client.post("/v1/user/register/", data, format="json") - self.assertEquals(request.status_code, 201) + self.assertEqual(request.status_code, 201) created_data: Dict = request.data for k, v in check_fields: - self.assertEquals(created_data[k], v) + self.assertEqual(created_data[k], v) send_valid_data( { @@ -185,11 +185,11 @@ def test_register_read_only_fields(self): def send_valid_data(data, check_fields=[]): request = self.client.post("/v1/user/register/", data, format="json") - self.assertEquals(request.status_code, 201) + self.assertEqual(request.status_code, 201) created_data: Dict = request.data for k, v in check_fields: - self.assertEquals(created_data[k], v) + self.assertEqual(created_data[k], v) send_valid_data( { @@ -277,10 +277,10 @@ def test_register_can_confirm_email(self): username = match["username"] token = match["token"] - self.assertEquals(username, data["username"]) + self.assertEqual(username, data["username"]) request = self.client.get(f"/v1/user/confirm/{username}/{token}") - self.assertEquals(request.status_code, 200) + self.assertEqual(request.status_code, 200) self.assertTrue(User.objects.get(username=data["username"]).email_active) @@ -307,10 +307,10 @@ def test_can_confirm_email_only_once(self): token = match["token"] request = self.client.get(f"/v1/user/confirm/{username}/{token}") - self.assertEquals(request.status_code, 200) + self.assertEqual(request.status_code, 200) request = self.client.get(f"/v1/user/confirm/{username}/{token}") - self.assertEquals(request.status_code, 400) + self.assertEqual(request.status_code, 400) def test_confirmation_email_is_token_checked(self): """ @@ -339,7 +339,7 @@ def test_confirmation_email_is_token_checked(self): request = self.client.get(f"/v1/user/confirm/{username}/{token}") - self.assertEquals(request.status_code, 400) + self.assertEqual(request.status_code, 400) def test_login_invalid_account(self): """ @@ -349,8 +349,8 @@ def test_login_invalid_account(self): def send_valid_data(data): request = self.client.post("/v1/user/login/", data, format="json") - self.assertEquals(request.status_code, 404) - self.assertEquals( + self.assertEqual(request.status_code, 404) + self.assertEqual( request.data["user"][0], _("Nom d'utilisateur ou mot de passe incorrect"), ) @@ -495,7 +495,7 @@ def test_can_reset_password(self): # self.assertFalse(User.objects.get(username=data["username"]).email_active) - # self.assertEquals(username, data["username"]) + # self.assertEqual(username, data["username"]) data = { "user": username, "token": token, @@ -506,7 +506,7 @@ def test_can_reset_password(self): request = self.client.post( "/v1/user/password-reset/submit/", data, format="json" ) - self.assertEquals(request.status_code, 200) + self.assertEqual(request.status_code, 200) self.client.post("/v1/user/logout/", format="json") login_data = { @@ -514,7 +514,7 @@ def test_can_reset_password(self): "password": "UwU*nuzzles*621!", } request = self.client.post("/v1/user/login/", login_data, format="json") - self.assertEquals(request.status_code, 200) + self.assertEqual(request.status_code, 200) self.client.post("/v1/user/logout/", format="json") login_data = { @@ -522,7 +522,7 @@ def test_can_reset_password(self): "password": "IUseAVerySecurePassword", } request = self.client.post("/v1/user/login/", login_data, format="json") - self.assertEquals(request.status_code, 404) + self.assertEqual(request.status_code, 404) def test_can_reset_password_only_once(self): """ @@ -553,7 +553,7 @@ def test_can_reset_password_only_once(self): request = self.client.post( "/v1/user/password-reset/submit/", data, format="json" ) - self.assertEquals(request.status_code, 200) + self.assertEqual(request.status_code, 200) data = { "user": username, @@ -565,7 +565,7 @@ def test_can_reset_password_only_once(self): request = self.client.post( "/v1/user/password-reset/submit/", data, format="json" ) - self.assertEquals(request.status_code, 400) + self.assertEqual(request.status_code, 400) def test_password_reset_is_token_checked(self): """ @@ -599,4 +599,4 @@ def test_password_reset_is_token_checked(self): request = self.client.post( "/v1/user/password-reset/submit/", data, format="json" ) - self.assertEquals(request.status_code, 400) + self.assertEqual(request.status_code, 400) diff --git a/insalan/user/views.py b/insalan/user/views.py index 0c0b60c8..5a49120c 100644 --- a/insalan/user/views.py +++ b/insalan/user/views.py @@ -30,6 +30,9 @@ @require_GET @ensure_csrf_cookie def get_csrf(request): + """ + Returns a response setting CSRF cookie in headers + """ return JsonResponse({"csrf": _("Le cookie a été défini")}) @@ -54,6 +57,9 @@ class UserMe(APIView): permission_classes = [permissions.IsAuthenticated] def get(self, request): + """ + Returns an user's own informations + """ user = UserSerializer(request.user) return Response(user.data) @@ -170,24 +176,21 @@ def post(self, request): user_object.set_password(data["password"]) user_object.save() return Response() - else: - return Response( - { - "user": [_("Mot de passe trop simple ou invalide")], - "errors": validation_errors, - }, - status=status.HTTP_400_BAD_REQUEST, - ) - else: return Response( - {"user": [_("Les mots de passe diffèrent")]}, + { + "user": [_("Mot de passe trop simple ou invalide")], + "errors": validation_errors, + }, status=status.HTTP_400_BAD_REQUEST, ) - else: return Response( - {"user": [_("Jeton de ré-initialisation invalide")]}, + {"user": [_("Les mots de passe diffèrent")]}, status=status.HTTP_400_BAD_REQUEST, ) + return Response( + {"user": [_("Jeton de ré-initialisation invalide")]}, + status=status.HTTP_400_BAD_REQUEST, + ) except User.DoesNotExist: return Response( @@ -257,6 +260,9 @@ class UserLogin(APIView): authentication_classes = [SessionAuthentication] def post(self, request): + """ + Submit a login form + """ data = request.data serializer = UserLoginSerializer(data=data) if serializer.is_valid(): From 1e14105158b7fe23dc5c21ec422f7effcddd9065 Mon Sep 17 00:00:00 2001 From: Lugrim Date: Tue, 10 Oct 2023 10:31:40 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8C=90=20Uniformized=20inclusive=20wr?= =?UTF-8?q?iting=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- insalan/user/apps.py | 2 +- insalan/user/models.py | 2 +- insalan/user/tests.py | 2 +- insalan/user/views.py | 6 +- locale/en/LC_MESSAGES/django.po | 104 +++++++++++++++++--------------- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/insalan/user/apps.py b/insalan/user/apps.py index 7ef43e30..b246481f 100644 --- a/insalan/user/apps.py +++ b/insalan/user/apps.py @@ -10,5 +10,5 @@ class UserConfig(AppConfig): """Configuration of the User Django App""" name = "insalan.user" - verbose_name = _("Gestionnaire d'utilisateur⋅ices") + verbose_name = _("Gestionnaire d'utilisateur⋅rices") default_auto_field = "django.db.models.BigAutoField" diff --git a/insalan/user/models.py b/insalan/user/models.py index 984725a2..2fdbc37e 100644 --- a/insalan/user/models.py +++ b/insalan/user/models.py @@ -71,7 +71,7 @@ class User(AbstractUser, PermissionsMixin): class Meta: """Meta options""" - verbose_name = _("Utilisateur⋅ice") + verbose_name = _("Utilisateur⋅rice") verbose_name_plural = _("Utilisateur⋅ices") def __init__(self, *args, **kwargs): diff --git a/insalan/user/tests.py b/insalan/user/tests.py index 8a322771..83a96974 100644 --- a/insalan/user/tests.py +++ b/insalan/user/tests.py @@ -352,7 +352,7 @@ def send_valid_data(data): self.assertEqual(request.status_code, 404) self.assertEqual( request.data["user"][0], - _("Nom d'utilisateur ou mot de passe incorrect"), + _("Nom d'utilisateur·rice ou mot de passe incorrect"), ) send_valid_data( diff --git a/insalan/user/views.py b/insalan/user/views.py index 5a49120c..b371291c 100644 --- a/insalan/user/views.py +++ b/insalan/user/views.py @@ -97,7 +97,7 @@ def get(self, request, user=None, token=None): """ If requested with valid parameters, will validate an user's email """ - error_text = _("Utilisateur ou jeton invalide (ou adresse déjà confirmée)") + error_text = _("Utilisateur·rice ou jeton invalide (ou adresse déjà confirmée)") if user and token: try: @@ -194,7 +194,7 @@ def post(self, request): except User.DoesNotExist: return Response( - {"user": [_("Utilisateur⋅ice non trouvé⋅e")]}, + {"user": [_("Utilisateur⋅rice non trouvé⋅e")]}, status=status.HTTP_400_BAD_REQUEST, ) @@ -269,7 +269,7 @@ def post(self, request): user = serializer.check_validity(data) if user is None: return Response( - {"user": [_("Nom d'utilisateur ou mot de passe incorrect")]}, + {"user": [_("Nom d'utilisateur·rice ou mot de passe incorrect")]}, status=status.HTTP_404_NOT_FOUND, ) login(request, user) diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 02a8b7e1..ff2fa61f 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -2,13 +2,14 @@ # Copyright (C) 2023 INSALAN # This file is distributed under the same license as the insalan package. # Amélie Gonzalez , 2023. +# Aurore Poirier , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-10-07 22:28+0000\n" +"POT-Creation-Date: 2023-10-10 08:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Aurore Poirier \n" "Language-Team: SysRez-Dev \n" @@ -39,32 +40,32 @@ msgstr "No ongoing event" msgid "Partenaires & Sponsors" msgstr "Partners & Sponsors" -#: insalan/partner/models.py:14 insalan/partner/models.py:20 +#: insalan/partner/models.py:11 insalan/partner/models.py:17 msgid "Partenaire" msgstr "Partner" -#: insalan/partner/models.py:15 +#: insalan/partner/models.py:12 msgid "Sponsor" msgstr "Sponsor" -#: insalan/partner/models.py:21 +#: insalan/partner/models.py:18 msgid "Partenaires" msgstr "Partners" -#: insalan/partner/models.py:25 +#: insalan/partner/models.py:22 msgid "Nom du partenaire/sponsor" msgstr "Partner/Sponsor name" -#: insalan/partner/models.py:27 +#: insalan/partner/models.py:24 msgid "URL" msgstr "URL" -#: insalan/partner/models.py:29 insalan/tournament/models.py:60 -#: insalan/tournament/models.py:156 +#: insalan/partner/models.py:26 insalan/tournament/models.py:58 +#: insalan/tournament/models.py:154 msgid "Logo" msgstr "Logo" -#: insalan/partner/models.py:36 +#: insalan/partner/models.py:33 msgid "Type de partenariat" msgstr "Partnership Type" @@ -109,7 +110,7 @@ msgid "UUID" msgstr "UUID" #: insalan/tickets/models.py:25 insalan/tournament/models.py:293 -#: insalan/tournament/models.py:354 insalan/user/models.py:73 +#: insalan/tournament/models.py:354 msgid "Utilisateur⋅ice" msgstr "User" @@ -124,7 +125,6 @@ msgid "UUID invalide" msgstr "Invalid UUID" #: insalan/tickets/tests.py:93 insalan/tickets/views.py:32 -#: insalan/user/views.py:164 msgid "Utilisateur⋅ice non trouvé⋅e" msgstr "User not found" @@ -143,76 +143,76 @@ msgstr "Ticket canceled" msgid "Ticket déjà scanné" msgstr "Ticket already scanned" -#: insalan/tournament/apps.py:11 insalan/tournament/models.py:168 +#: insalan/tournament/apps.py:11 insalan/tournament/models.py:166 msgid "Tournois" msgstr "Tournaments" -#: insalan/tournament/models.py:37 +#: insalan/tournament/models.py:35 msgid "Nom de l'évènement" msgstr "Event name" -#: insalan/tournament/models.py:43 +#: insalan/tournament/models.py:41 msgid "Description de l'évènement" msgstr "Event description" -#: insalan/tournament/models.py:46 +#: insalan/tournament/models.py:44 msgid "Année" msgstr "Year" -#: insalan/tournament/models.py:51 +#: insalan/tournament/models.py:49 msgid "Mois" msgstr "Month" -#: insalan/tournament/models.py:56 +#: insalan/tournament/models.py:54 msgid "En cours" msgstr "Ongoing" -#: insalan/tournament/models.py:71 insalan/tournament/models.py:135 +#: insalan/tournament/models.py:69 insalan/tournament/models.py:133 msgid "Évènement" msgstr "Event" -#: insalan/tournament/models.py:72 +#: insalan/tournament/models.py:70 msgid "Évènements" msgstr "Events" -#: insalan/tournament/models.py:98 insalan/tournament/models.py:140 +#: insalan/tournament/models.py:96 insalan/tournament/models.py:138 msgid "Jeu" msgstr "Game" -#: insalan/tournament/models.py:99 +#: insalan/tournament/models.py:97 msgid "Jeux" msgstr "Games" -#: insalan/tournament/models.py:102 +#: insalan/tournament/models.py:100 msgid "Nom du jeux" msgstr "Game name" -#: insalan/tournament/models.py:108 +#: insalan/tournament/models.py:106 msgid "Nom raccourci du jeu" msgstr "Short game name" -#: insalan/tournament/models.py:144 +#: insalan/tournament/models.py:142 msgid "Nom du tournoi" msgstr "Tournament name" -#: insalan/tournament/models.py:149 +#: insalan/tournament/models.py:147 msgid "Règlement du tournoi" msgstr "Tournament rules" -#: insalan/tournament/models.py:167 insalan/tournament/models.py:210 +#: insalan/tournament/models.py:165 insalan/tournament/models.py:208 msgid "Tournoi" msgstr "Tournament" -#: insalan/tournament/models.py:216 +#: insalan/tournament/models.py:214 msgid "Nom d'équipe" msgstr "Team name" -#: insalan/tournament/models.py:221 insalan/tournament/models.py:298 +#: insalan/tournament/models.py:219 insalan/tournament/models.py:298 #: insalan/tournament/models.py:358 msgid "Équipe" msgstr "Team" -#: insalan/tournament/models.py:222 +#: insalan/tournament/models.py:220 msgid "Équipes" msgstr "Teams" @@ -253,7 +253,7 @@ msgid "Inscriptions de managers" msgstr "Manager Registrations" #: insalan/user/apps.py:13 -msgid "Gestionnaire d'utilisateur⋅ices" +msgid "Gestionnaire d'utilisateur⋅rices" msgstr "User Management Application" #: insalan/user/models.py:34 @@ -268,42 +268,46 @@ msgstr "An username is required" msgid "Un mot de passe est requis" msgstr "A password is required" -#: insalan/user/models.py:52 +#: insalan/user/models.py:54 msgid "Les superutilisateur·rices requièrent un mot de passe" msgstr "Superusers require a password" #: insalan/user/models.py:74 +msgid "Utilisateur⋅rice" +msgstr "User" + +#: insalan/user/models.py:75 msgid "Utilisateur⋅ices" msgstr "Users" -#: insalan/user/models.py:83 +#: insalan/user/models.py:84 msgid "Courriel" msgstr "Email" -#: insalan/user/models.py:86 +#: insalan/user/models.py:87 msgid "Courriel vérifié" msgstr "Email activated" -#: insalan/user/models.py:127 +#: insalan/user/models.py:128 msgid "Confirmez votre courriel" msgstr "Confirm your email" -#: insalan/user/models.py:128 +#: insalan/user/models.py:129 msgid "Confirmez votre adresse de courriel en cliquant sur " msgstr "Confirm your email by clicking on " -#: insalan/user/models.py:145 +#: insalan/user/models.py:147 msgid "Demande de ré-initialisation de mot de passe" msgstr "Ask for a password reset" -#: insalan/user/models.py:147 +#: insalan/user/models.py:149 msgid "" "Une demande de ré-initialisation de mot de passe a été effectuéepour votre " "compte. Si vous êtes à l'origine de cette demande,vous pouvez cliquer sur le " "lien suivant: " msgstr "" -#: insalan/user/serializers.py:63 insalan/user/views.py:153 +#: insalan/user/serializers.py:63 insalan/user/views.py:187 msgid "Les mots de passe diffèrent" msgstr "Passwords are different" @@ -311,34 +315,38 @@ msgstr "Passwords are different" msgid "Compte non activé" msgstr "Account not activated" -#: insalan/user/tests.py:301 insalan/user/views.py:233 -msgid "Nom d'utilisateur ou mot de passe incorrect" +#: insalan/user/tests.py:355 insalan/user/views.py:272 +msgid "Nom d'utilisateur·rice ou mot de passe incorrect" msgstr "Username or password incorrect" -#: insalan/user/views.py:33 +#: insalan/user/views.py:36 msgid "Le cookie a été défini" msgstr "Cookie has been defined" -#: insalan/user/views.py:79 -msgid "Utilisateur ou jeton invalide (ou adresse déjà confirmée)" +#: insalan/user/views.py:100 +msgid "Utilisateur·rice ou jeton invalide (ou adresse déjà confirmée)" msgstr "User or token invalid, or account is already confirmed" -#: insalan/user/views.py:129 +#: insalan/user/views.py:165 msgid "Champ manquant" msgstr "Missing field" -#: insalan/user/views.py:146 +#: insalan/user/views.py:181 msgid "Mot de passe trop simple ou invalide" msgstr "Password too simple or invalid" -#: insalan/user/views.py:158 +#: insalan/user/views.py:191 msgid "Jeton de ré-initialisation invalide" msgstr "Reset token invalid" -#: insalan/user/views.py:180 +#: insalan/user/views.py:197 +msgid "Utilisateur⋅rice non trouvé⋅e" +msgstr "User not found" + +#: insalan/user/views.py:216 msgid "Impossible de renvoyer le courriel de confirmation" msgstr "Impossible to send confirmation email again" -#: insalan/user/views.py:239 +#: insalan/user/views.py:278 msgid "Format des données soumises invalide" msgstr "Submitted data format invalid"