diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b334a7c0..ab1fbf56 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.9.10 + rev: v0.11.4 hooks: - id: ruff # just check the code, and print the errors - id: ruff # actually fix the fixable errors, but print nothing diff --git a/accounting/__init__.py b/accounting/__init__.py deleted file mode 100644 index f4445e69..00000000 --- a/accounting/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# -# Copyright 2023 © AE UTBM -# ae@utbm.fr / ae.info@utbm.fr -# -# This file is part of the website of the UTBM Student Association (AE UTBM), -# https://ae.utbm.fr. -# -# You can find the source code of the website at https://github.com/ae-utbm/sith -# -# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) -# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE -# OR WITHIN THE LOCAL FILE "LICENSE" -# -# diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py deleted file mode 100644 index 29ce0739..00000000 --- a/accounting/migrations/0001_initial.py +++ /dev/null @@ -1,280 +0,0 @@ -from __future__ import unicode_literals - -import django.core.validators -import django.db.models.deletion -from django.db import migrations, models - -import counter.fields - - -class Migration(migrations.Migration): - dependencies = [] - - operations = [ - migrations.CreateModel( - name="AccountingType", - fields=[ - ( - "id", - models.AutoField( - primary_key=True, - serialize=False, - verbose_name="ID", - auto_created=True, - ), - ), - ( - "code", - models.CharField( - max_length=16, - verbose_name="code", - validators=[ - django.core.validators.RegexValidator( - "^[0-9]*$", - "An accounting type code contains only numbers", - ) - ], - ), - ), - ("label", models.CharField(max_length=128, verbose_name="label")), - ( - "movement_type", - models.CharField( - choices=[ - ("CREDIT", "Credit"), - ("DEBIT", "Debit"), - ("NEUTRAL", "Neutral"), - ], - max_length=12, - verbose_name="movement type", - ), - ), - ], - options={ - "verbose_name": "accounting type", - "ordering": ["movement_type", "code"], - }, - ), - migrations.CreateModel( - name="BankAccount", - fields=[ - ( - "id", - models.AutoField( - primary_key=True, - serialize=False, - verbose_name="ID", - auto_created=True, - ), - ), - ("name", models.CharField(max_length=30, verbose_name="name")), - ( - "iban", - models.CharField(max_length=255, blank=True, verbose_name="iban"), - ), - ( - "number", - models.CharField( - max_length=255, blank=True, verbose_name="account number" - ), - ), - ], - options={"verbose_name": "Bank account", "ordering": ["club", "name"]}, - ), - migrations.CreateModel( - name="ClubAccount", - fields=[ - ( - "id", - models.AutoField( - primary_key=True, - serialize=False, - verbose_name="ID", - auto_created=True, - ), - ), - ("name", models.CharField(max_length=30, verbose_name="name")), - ], - options={ - "verbose_name": "Club account", - "ordering": ["bank_account", "name"], - }, - ), - migrations.CreateModel( - name="Company", - fields=[ - ( - "id", - models.AutoField( - primary_key=True, - serialize=False, - verbose_name="ID", - auto_created=True, - ), - ), - ("name", models.CharField(max_length=60, verbose_name="name")), - ], - options={"verbose_name": "company"}, - ), - migrations.CreateModel( - name="GeneralJournal", - fields=[ - ( - "id", - models.AutoField( - primary_key=True, - serialize=False, - verbose_name="ID", - auto_created=True, - ), - ), - ("start_date", models.DateField(verbose_name="start date")), - ( - "end_date", - models.DateField( - null=True, verbose_name="end date", default=None, blank=True - ), - ), - ("name", models.CharField(max_length=40, verbose_name="name")), - ( - "closed", - models.BooleanField(verbose_name="is closed", default=False), - ), - ( - "amount", - counter.fields.CurrencyField( - decimal_places=2, - default=0, - verbose_name="amount", - max_digits=12, - ), - ), - ( - "effective_amount", - counter.fields.CurrencyField( - decimal_places=2, - default=0, - verbose_name="effective_amount", - max_digits=12, - ), - ), - ], - options={"verbose_name": "General journal", "ordering": ["-start_date"]}, - ), - migrations.CreateModel( - name="Operation", - fields=[ - ( - "id", - models.AutoField( - primary_key=True, - serialize=False, - verbose_name="ID", - auto_created=True, - ), - ), - ("number", models.IntegerField(verbose_name="number")), - ( - "amount", - counter.fields.CurrencyField( - decimal_places=2, max_digits=12, verbose_name="amount" - ), - ), - ("date", models.DateField(verbose_name="date")), - ("remark", models.CharField(max_length=128, verbose_name="comment")), - ( - "mode", - models.CharField( - choices=[ - ("CHECK", "Check"), - ("CASH", "Cash"), - ("TRANSFERT", "Transfert"), - ("CARD", "Credit card"), - ], - max_length=255, - verbose_name="payment method", - ), - ), - ( - "cheque_number", - models.CharField( - max_length=32, - null=True, - verbose_name="cheque number", - default="", - blank=True, - ), - ), - ("done", models.BooleanField(verbose_name="is done", default=False)), - ( - "target_type", - models.CharField( - choices=[ - ("USER", "User"), - ("CLUB", "Club"), - ("ACCOUNT", "Account"), - ("COMPANY", "Company"), - ("OTHER", "Other"), - ], - max_length=10, - verbose_name="target type", - ), - ), - ( - "target_id", - models.IntegerField( - null=True, verbose_name="target id", blank=True - ), - ), - ( - "target_label", - models.CharField( - max_length=32, - blank=True, - verbose_name="target label", - default="", - ), - ), - ( - "accounting_type", - models.ForeignKey( - null=True, - related_name="operations", - verbose_name="accounting type", - to="accounting.AccountingType", - blank=True, - on_delete=django.db.models.deletion.CASCADE, - ), - ), - ], - options={"ordering": ["-number"]}, - ), - migrations.CreateModel( - name="SimplifiedAccountingType", - fields=[ - ( - "id", - models.AutoField( - primary_key=True, - serialize=False, - verbose_name="ID", - auto_created=True, - ), - ), - ("label", models.CharField(max_length=128, verbose_name="label")), - ( - "accounting_type", - models.ForeignKey( - verbose_name="simplified accounting types", - to="accounting.AccountingType", - related_name="simplified_types", - on_delete=django.db.models.deletion.CASCADE, - ), - ), - ], - options={ - "verbose_name": "simplified type", - "ordering": ["accounting_type__movement_type", "accounting_type__code"], - }, - ), - ] diff --git a/accounting/migrations/0002_auto_20160824_2152.py b/accounting/migrations/0002_auto_20160824_2152.py deleted file mode 100644 index adfc0225..00000000 --- a/accounting/migrations/0002_auto_20160824_2152.py +++ /dev/null @@ -1,105 +0,0 @@ -from __future__ import unicode_literals - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("club", "0001_initial"), - ("accounting", "0001_initial"), - ("core", "0001_initial"), - ] - - operations = [ - migrations.AddField( - model_name="operation", - name="invoice", - field=models.ForeignKey( - null=True, - related_name="operations", - verbose_name="invoice", - to="core.SithFile", - blank=True, - on_delete=django.db.models.deletion.CASCADE, - ), - ), - migrations.AddField( - model_name="operation", - name="journal", - field=models.ForeignKey( - verbose_name="journal", - to="accounting.GeneralJournal", - related_name="operations", - on_delete=django.db.models.deletion.CASCADE, - ), - ), - migrations.AddField( - model_name="operation", - name="linked_operation", - field=models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - blank=True, - to="accounting.Operation", - null=True, - related_name="operation_linked_to", - verbose_name="linked operation", - default=None, - ), - ), - migrations.AddField( - model_name="operation", - name="simpleaccounting_type", - field=models.ForeignKey( - null=True, - related_name="operations", - verbose_name="simple type", - to="accounting.SimplifiedAccountingType", - blank=True, - on_delete=django.db.models.deletion.CASCADE, - ), - ), - migrations.AddField( - model_name="generaljournal", - name="club_account", - field=models.ForeignKey( - verbose_name="club account", - to="accounting.ClubAccount", - related_name="journals", - on_delete=django.db.models.deletion.CASCADE, - ), - ), - migrations.AddField( - model_name="clubaccount", - name="bank_account", - field=models.ForeignKey( - verbose_name="bank account", - to="accounting.BankAccount", - related_name="club_accounts", - on_delete=django.db.models.deletion.CASCADE, - ), - ), - migrations.AddField( - model_name="clubaccount", - name="club", - field=models.ForeignKey( - verbose_name="club", - to="club.Club", - related_name="club_account", - on_delete=django.db.models.deletion.CASCADE, - ), - ), - migrations.AddField( - model_name="bankaccount", - name="club", - field=models.ForeignKey( - verbose_name="club", - to="club.Club", - related_name="bank_accounts", - on_delete=django.db.models.deletion.CASCADE, - ), - ), - migrations.AlterUniqueTogether( - name="operation", unique_together={("number", "journal")} - ), - ] diff --git a/accounting/migrations/0003_auto_20160824_2203.py b/accounting/migrations/0003_auto_20160824_2203.py deleted file mode 100644 index 7fa3910a..00000000 --- a/accounting/migrations/0003_auto_20160824_2203.py +++ /dev/null @@ -1,48 +0,0 @@ -from __future__ import unicode_literals - -import phonenumber_field.modelfields -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("accounting", "0002_auto_20160824_2152")] - - operations = [ - migrations.AddField( - model_name="company", - name="city", - field=models.CharField(blank=True, verbose_name="city", max_length=60), - ), - migrations.AddField( - model_name="company", - name="country", - field=models.CharField(blank=True, verbose_name="country", max_length=32), - ), - migrations.AddField( - model_name="company", - name="email", - field=models.EmailField(blank=True, verbose_name="email", max_length=254), - ), - migrations.AddField( - model_name="company", - name="phone", - field=phonenumber_field.modelfields.PhoneNumberField( - blank=True, verbose_name="phone", max_length=128 - ), - ), - migrations.AddField( - model_name="company", - name="postcode", - field=models.CharField(blank=True, verbose_name="postcode", max_length=10), - ), - migrations.AddField( - model_name="company", - name="street", - field=models.CharField(blank=True, verbose_name="street", max_length=60), - ), - migrations.AddField( - model_name="company", - name="website", - field=models.CharField(blank=True, verbose_name="website", max_length=64), - ), - ] diff --git a/accounting/migrations/0004_auto_20161005_1505.py b/accounting/migrations/0004_auto_20161005_1505.py deleted file mode 100644 index fe8fd3c4..00000000 --- a/accounting/migrations/0004_auto_20161005_1505.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import unicode_literals - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("accounting", "0003_auto_20160824_2203")] - - operations = [ - migrations.CreateModel( - name="Label", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - primary_key=True, - auto_created=True, - serialize=False, - ), - ), - ("name", models.CharField(max_length=64, verbose_name="label")), - ( - "club_account", - models.ForeignKey( - related_name="labels", - verbose_name="club account", - to="accounting.ClubAccount", - on_delete=django.db.models.deletion.CASCADE, - ), - ), - ], - ), - migrations.AddField( - model_name="operation", - name="label", - field=models.ForeignKey( - on_delete=django.db.models.deletion.SET_NULL, - related_name="operations", - null=True, - blank=True, - verbose_name="label", - to="accounting.Label", - ), - ), - migrations.AlterUniqueTogether( - name="label", unique_together={("name", "club_account")} - ), - ] diff --git a/accounting/migrations/0005_auto_20170324_0917.py b/accounting/migrations/0005_auto_20170324_0917.py deleted file mode 100644 index 3f640fd0..00000000 --- a/accounting/migrations/0005_auto_20170324_0917.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("accounting", "0004_auto_20161005_1505")] - - operations = [ - migrations.AlterField( - model_name="operation", - name="remark", - field=models.CharField( - null=True, max_length=128, blank=True, verbose_name="comment" - ), - ) - ] diff --git a/accounting/migrations/0006_remove_all_models.py b/accounting/migrations/0006_remove_all_models.py deleted file mode 100644 index 96add19c..00000000 --- a/accounting/migrations/0006_remove_all_models.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 4.2.20 on 2025-03-14 16:06 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [("accounting", "0005_auto_20170324_0917")] - - operations = [ - migrations.RemoveField(model_name="bankaccount", name="club"), - migrations.RemoveField(model_name="clubaccount", name="bank_account"), - migrations.RemoveField(model_name="clubaccount", name="club"), - migrations.DeleteModel(name="Company"), - migrations.RemoveField(model_name="generaljournal", name="club_account"), - migrations.AlterUniqueTogether(name="label", unique_together=None), - migrations.RemoveField(model_name="label", name="club_account"), - migrations.AlterUniqueTogether(name="operation", unique_together=None), - migrations.RemoveField(model_name="operation", name="accounting_type"), - migrations.RemoveField(model_name="operation", name="invoice"), - migrations.RemoveField(model_name="operation", name="journal"), - migrations.RemoveField(model_name="operation", name="label"), - migrations.RemoveField(model_name="operation", name="linked_operation"), - migrations.RemoveField(model_name="operation", name="simpleaccounting_type"), - migrations.RemoveField( - model_name="simplifiedaccountingtype", name="accounting_type" - ), - migrations.DeleteModel(name="AccountingType"), - migrations.DeleteModel(name="BankAccount"), - migrations.DeleteModel(name="ClubAccount"), - migrations.DeleteModel(name="GeneralJournal"), - migrations.DeleteModel(name="Label"), - migrations.DeleteModel(name="Operation"), - migrations.DeleteModel(name="SimplifiedAccountingType"), - ] diff --git a/accounting/migrations/__init__.py b/accounting/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/accounting/models.py b/accounting/models.py deleted file mode 100644 index f4445e69..00000000 --- a/accounting/models.py +++ /dev/null @@ -1,14 +0,0 @@ -# -# Copyright 2023 © AE UTBM -# ae@utbm.fr / ae.info@utbm.fr -# -# This file is part of the website of the UTBM Student Association (AE UTBM), -# https://ae.utbm.fr. -# -# You can find the source code of the website at https://github.com/ae-utbm/sith -# -# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) -# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE -# OR WITHIN THE LOCAL FILE "LICENSE" -# -# diff --git a/antispam/forms.py b/antispam/forms.py index a2f97124..0e327f2a 100644 --- a/antispam/forms.py +++ b/antispam/forms.py @@ -1,5 +1,3 @@ -import re - from django import forms from django.core.validators import EmailValidator from django.utils.translation import gettext_lazy as _ @@ -7,12 +5,18 @@ from django.utils.translation import gettext_lazy as _ from antispam.models import ToxicDomain +class AntiSpamEmailValidator(EmailValidator): + def __call__(self, value: str): + super().__call__(value) + domain_part = value.rsplit("@", 1)[1] + if ToxicDomain.objects.filter(domain=domain_part).exists(): + raise forms.ValidationError(_("Email domain is not allowed.")) + + +validate_antispam_email = AntiSpamEmailValidator() + + class AntiSpamEmailField(forms.EmailField): """An email field that email addresses with a known toxic domain.""" - def run_validators(self, value: str): - super().run_validators(value) - # Domain part should exist since email validation is guaranteed to run first - domain = re.search(EmailValidator.domain_regex, value) - if ToxicDomain.objects.filter(domain=domain[0]).exists(): - raise forms.ValidationError(_("Email domain is not allowed.")) + default_validators = [validate_antispam_email] diff --git a/antispam/management/commands/update_spam_database.py b/antispam/management/commands/update_spam_database.py index 4e9d0ec8..2761c649 100644 --- a/antispam/management/commands/update_spam_database.py +++ b/antispam/management/commands/update_spam_database.py @@ -34,7 +34,7 @@ class Command(BaseCommand): f"Source {provider} responded with code {res.status_code}" ) continue - domains |= set(res.content.decode().splitlines()) + domains |= set(res.text.splitlines()) return domains def _update_domains(self, domains: set[str]): diff --git a/club/forms.py b/club/forms.py index ab8f5757..da0f5fb7 100644 --- a/club/forms.py +++ b/club/forms.py @@ -196,9 +196,7 @@ class ClubMemberForm(forms.Form): self.request_user = kwargs.pop("request_user") self.club_members = kwargs.pop("club_members", None) if not self.club_members: - self.club_members = ( - self.club.members.filter(end_date=None).order_by("-role").all() - ) + self.club_members = self.club.members.ongoing().order_by("-role").all() self.request_user_membership = self.club.get_membership_for(self.request_user) super().__init__(*args, **kwargs) diff --git a/club/templates/club/club_members.jinja b/club/templates/club/club_members.jinja index a64d43ce..0778b486 100644 --- a/club/templates/club/club_members.jinja +++ b/club/templates/club/club_members.jinja @@ -11,7 +11,7 @@ {{ select_all_checkbox("users_old") }}

{% endif %} - +
diff --git a/club/tests/base.py b/club/tests/base.py index 47f50264..8ae8f3f4 100644 --- a/club/tests/base.py +++ b/club/tests/base.py @@ -6,8 +6,10 @@ from django.test import TestCase from django.urls import reverse from django.utils.timezone import now from model_bakery import baker +from model_bakery.recipe import Recipe from club.models import Club, Membership +from core.baker_recipes import old_subscriber_user, subscriber_user from core.models import User @@ -17,8 +19,9 @@ class TestClub(TestCase): The generated dataset is the one created by the populate command, plus the following modifications : - - `self.club` is a dummy club recreated for each test - - `self.club` has two board members : skia (role 3) and comptable (role 10) + - `self.club` is a dummy club + - `self.club` has two board members : + simple_board_member (role 3) and president (role 10) - `self.club` has one regular member : richard - `self.club` has one former member : sli (who had role 2) - None of the `self.club` members are in the AE club. @@ -27,44 +30,30 @@ class TestClub(TestCase): @classmethod def setUpTestData(cls): # subscribed users - initial members - cls.skia = User.objects.get(username="skia") - # by default, Skia is in the AE, which creates side effect - cls.skia.memberships.all().delete() + cls.president, cls.simple_board_member = subscriber_user.make(_quantity=2) cls.richard = User.objects.get(username="rbatsbak") - cls.comptable = User.objects.get(username="comptable") cls.sli = User.objects.get(username="sli") - cls.root = User.objects.get(username="root") + cls.root = baker.make(User, is_superuser=True) + cls.old_subscriber = old_subscriber_user.make() + cls.public = baker.make(User) - # subscribed users - not initial members + # subscribed users - not initial member cls.krophil = User.objects.get(username="krophil") - cls.subscriber = User.objects.get(username="subscriber") - - # old subscriber - cls.old_subscriber = User.objects.get(username="old_subscriber") - - # not subscribed - cls.public = User.objects.get(username="public") + cls.subscriber = subscriber_user.make() cls.ae = Club.objects.get(pk=settings.SITH_MAIN_CLUB_ID) cls.club = baker.make(Club) cls.members_url = reverse("club:club_members", kwargs={"club_id": cls.club.id}) a_month_ago = now() - timedelta(days=30) yesterday = now() - timedelta(days=1) - Membership.objects.create( - club=cls.club, user=cls.skia, start_date=a_month_ago, role=3 + membership_recipe = Recipe(Membership, club=cls.club) + membership_recipe.make( + user=cls.simple_board_member, start_date=a_month_ago, role=3 ) - Membership.objects.create(club=cls.club, user=cls.richard, role=1) - Membership.objects.create( - club=cls.club, user=cls.comptable, start_date=a_month_ago, role=10 - ) - - # sli was a member but isn't anymore - Membership.objects.create( - club=cls.club, - user=cls.sli, - start_date=a_month_ago, - end_date=yesterday, - role=2, + membership_recipe.make(user=cls.richard, role=1) + membership_recipe.make(user=cls.president, start_date=a_month_ago, role=10) + membership_recipe.make( # sli was a member but isn't anymore + user=cls.sli, start_date=a_month_ago, end_date=yesterday, role=2 ) def setUp(self): diff --git a/club/tests/test_mailing.py b/club/tests/test_mailing.py index 7076ac0c..e8ea3a9b 100644 --- a/club/tests/test_mailing.py +++ b/club/tests/test_mailing.py @@ -38,7 +38,7 @@ class TestMailingForm(TestCase): self.assertRedirects(response, self.mail_url) response = self.client.get(self.mail_url) assert response.status_code == 200 - assert "Liste de diffusion foyer@utbm.fr" in response.content.decode() + assert "Liste de diffusion foyer@utbm.fr" in response.text # Test with Root self.client.force_login(self.root) @@ -48,7 +48,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - assert "Liste de diffusion mde@utbm.fr" in response.content.decode() + assert "Liste de diffusion mde@utbm.fr" in response.text def test_mailing_list_add_moderation(self): self.client.force_login(self.rbatsbak) @@ -58,7 +58,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - content = response.content.decode() + content = response.text assert "Liste de diffusion mde@utbm.fr" not in content assert "

Listes de diffusions en attente de modération

" in content assert "
  • mde@utbm.fr" in content @@ -90,7 +90,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - assert "skia@git.an" not in response.content.decode() + assert "skia@git.an" not in response.text def test_add_new_subscription_success(self): # Prepare mailing list @@ -111,7 +111,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - assert "skia@git.an" in response.content.decode() + assert "skia@git.an" in response.text # Add multiple users self.client.post( @@ -124,7 +124,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - content = response.content.decode() + content = response.text assert "richard@git.an" in content assert "comunity@git.an" in content assert "skia@git.an" in content @@ -140,7 +140,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - content = response.content.decode() + content = response.text assert "richard@git.an" in content assert "comunity@git.an" in content assert "skia@git.an" in content @@ -158,7 +158,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - content = response.content.decode() + content = response.text assert "richard@git.an" in content assert "comunity@git.an" in content assert "skia@git.an" in content @@ -185,7 +185,7 @@ class TestMailingForm(TestCase): assert response.status_code self.assertInHTML( _("You must specify at least an user or an email address"), - response.content.decode(), + response.text, ) # No mailing specified @@ -197,7 +197,7 @@ class TestMailingForm(TestCase): }, ) assert response.status_code == 200 - assert _("This field is required") in response.content.decode() + assert _("This field is required") in response.text # One of the selected users doesn't exist response = self.client.post( @@ -211,7 +211,7 @@ class TestMailingForm(TestCase): assert response.status_code == 200 self.assertInHTML( _("You must specify at least an user or an email address"), - response.content.decode(), + response.text, ) # An user has no email address @@ -229,7 +229,7 @@ class TestMailingForm(TestCase): assert response.status_code == 200 self.assertInHTML( _("One of the selected users doesn't have an email address"), - response.content.decode(), + response.text, ) self.krophil.email = "krophil@git.an" @@ -257,7 +257,7 @@ class TestMailingForm(TestCase): assert response.status_code == 200 self.assertInHTML( _("This email is already suscribed in this mailing"), - response.content.decode(), + response.text, ) def test_remove_subscription_success(self): @@ -283,7 +283,7 @@ class TestMailingForm(TestCase): response = self.client.get(self.mail_url) assert response.status_code == 200 - content = response.content.decode() + content = response.text assert "comunity@git.an" in content assert "richard@git.an" in content @@ -299,7 +299,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - content = response.content.decode() + content = response.text assert "comunity@git.an" in content assert "richard@git.an" in content @@ -320,7 +320,7 @@ class TestMailingForm(TestCase): ) response = self.client.get(self.mail_url) assert response.status_code == 200 - content = response.content.decode() + content = response.text assert "comunity@git.an" not in content assert "richard@git.an" not in content diff --git a/club/tests/test_membership.py b/club/tests/test_membership.py index dd77e52e..ff668498 100644 --- a/club/tests/test_membership.py +++ b/club/tests/test_membership.py @@ -1,9 +1,12 @@ +from bs4 import BeautifulSoup from django.conf import settings from django.core.cache import cache +from django.db.models import Max from django.urls import reverse from django.utils.timezone import localdate, localtime, now from model_bakery import baker +from club.forms import ClubMemberForm from club.models import Membership from club.tests.base import TestClub from core.baker_recipes import subscriber_user @@ -17,8 +20,8 @@ class TestMembershipQuerySet(TestClub): """ current_members = list(self.club.members.ongoing().order_by("id")) expected = [ - self.skia.memberships.get(club=self.club), - self.comptable.memberships.get(club=self.club), + self.simple_board_member.memberships.get(club=self.club), + self.president.memberships.get(club=self.club), self.richard.memberships.get(club=self.club), ] expected.sort(key=lambda i: i.id) @@ -30,8 +33,8 @@ class TestMembershipQuerySet(TestClub): self.richard.memberships.filter(club=self.club).update(end_date=today) current_members = list(self.club.members.ongoing().order_by("id")) expected = [ - self.skia.memberships.get(club=self.club), - self.comptable.memberships.get(club=self.club), + self.simple_board_member.memberships.get(club=self.club), + self.president.memberships.get(club=self.club), ] expected.sort(key=lambda i: i.id) assert current_members == expected @@ -42,8 +45,8 @@ class TestMembershipQuerySet(TestClub): """ board_members = list(self.club.members.board().order_by("id")) expected = [ - self.skia.memberships.get(club=self.club), - self.comptable.memberships.get(club=self.club), + self.simple_board_member.memberships.get(club=self.club), + self.president.memberships.get(club=self.club), # sli is no more member, but he was in the board self.sli.memberships.get(club=self.club), ] @@ -56,17 +59,17 @@ class TestMembershipQuerySet(TestClub): """ members = list(self.club.members.ongoing().board().order_by("id")) expected = [ - self.skia.memberships.get(club=self.club), - self.comptable.memberships.get(club=self.club), + self.simple_board_member.memberships.get(club=self.club), + self.president.memberships.get(club=self.club), ] expected.sort(key=lambda i: i.id) assert members == expected def test_update_invalidate_cache(self): """Test that the `update` queryset method properly invalidate cache.""" - mem_skia = self.skia.memberships.get(club=self.club) + mem_skia = self.simple_board_member.memberships.get(club=self.club) cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia) - self.skia.memberships.update(end_date=localtime(now()).date()) + self.simple_board_member.memberships.update(end_date=localtime(now()).date()) assert ( cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}") == "not_member" @@ -104,14 +107,14 @@ class TestMembershipQuerySet(TestClub): def test_delete_invalidate_cache(self): """Test that the `delete` queryset properly invalidate cache.""" - mem_skia = self.skia.memberships.get(club=self.club) - mem_comptable = self.comptable.memberships.get(club=self.club) + mem_skia = self.simple_board_member.memberships.get(club=self.club) + mem_comptable = self.president.memberships.get(club=self.club) cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia) cache.set( f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}", mem_comptable ) - # should delete the subscriptions of skia and comptable + # should delete the subscriptions of simple_board_member and president self.club.members.ongoing().board().delete() for membership in (mem_skia, mem_comptable): @@ -167,36 +170,43 @@ class TestMembership(TestClub): """Test that a GET request return a page where the requested information are displayed. """ - self.client.force_login(self.skia) + self.client.force_login(self.simple_board_member) response = self.client.get(self.members_url) assert response.status_code == 200 - expected_html = ( - "
  • {% trans %}User{% endtrans %}
    " - "" - "" - "" - ) + soup = BeautifulSoup(response.text, "lxml") + table = soup.find("table", id="club_members_table") + assert [r.text for r in table.find("thead").find_all("td")] == [ + "Utilisateur", + "Rôle", + "Description", + "Depuis", + "Marquer comme ancien", + ] + rows = table.find("tbody").find_all("tr") memberships = self.club.members.ongoing().order_by("-role") - input_id = 0 - for membership in memberships.select_related("user"): + for row, membership in zip( + rows, memberships.select_related("user"), strict=False + ): user = membership.user - expected_html += ( - f'" - f"" - f"" - f"" - expected_html += "
    UtilisateurRôleDescriptionDepuisMarquer comme ancien
    ' - f"{user.get_display_name()}{settings.SITH_CLUB_ROLES[membership.role]}{membership.description}{membership.start_date}" - ) - if membership.role <= 3: # 3 is the role of skia - expected_html += ( - '' - ) - input_id += 1 - expected_html += "
    " - self.assertInHTML(expected_html, response.content.decode()) + user_url = reverse("core:user_profile", args=[user.id]) + cols = row.find_all("td") + user_link = cols[0].find("a") + assert user_link.attrs["href"] == user_url + assert user_link.text == user.get_display_name() + assert cols[1].text == settings.SITH_CLUB_ROLES[membership.role] + assert cols[2].text == membership.description + assert cols[3].text == str(membership.start_date) + + if membership.role <= 3: # 3 is the role of simple_board_member + form_input = cols[4].find("input") + expected_attrs = { + "type": "checkbox", + "name": "users_old", + "value": str(user.id), + } + assert form_input.attrs.items() >= expected_attrs.items() + else: + assert cols[4].find_all() == [] def test_root_add_one_club_member(self): """Test that root users can add members to clubs, one at a time.""" @@ -228,64 +238,61 @@ class TestMembership(TestClub): """Test that users who are not currently subscribed cannot be members of clubs. """ - self.client.force_login(self.root) - response = self.client.post( - self.members_url, - {"users": self.public.id, "role": 1}, - ) - assert not self.public.memberships.filter(club=self.club).exists() - assert '