mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-31 09:03:06 +00:00 
			
		
		
		
	Split ClubMemberForm into JoinClubForm and ClubAddMemberForm
This commit is contained in:
		| @@ -208,15 +208,14 @@ class ClubOldMemberForm(forms.Form): | ||||
|  | ||||
|  | ||||
| class ClubMemberForm(forms.ModelForm): | ||||
|     """Form handling the members of a club.""" | ||||
|     """Form to add a member to the club, as a board member.""" | ||||
|  | ||||
|     error_css_class = "error" | ||||
|     required_css_class = "required" | ||||
|  | ||||
|     class Meta: | ||||
|         model = Membership | ||||
|         fields = ["user", "role", "description"] | ||||
|         widgets = {"user": AutoCompleteSelectUser} | ||||
|         fields = ["role", "description"] | ||||
|  | ||||
|     def __init__(self, *args, club: Club, request_user: User, **kwargs): | ||||
|         self.club = club | ||||
| @@ -231,22 +230,36 @@ class ClubMemberForm(forms.ModelForm): | ||||
|         ] | ||||
|         self.instance.club = club | ||||
|  | ||||
|     @property | ||||
|     def max_available_role(self):  # pragma: no cover | ||||
|         """The greatest role that will be obtainable with this form.""" | ||||
|         # this is unreachable, because it will be overridden by subclasses | ||||
|         return -1 | ||||
|  | ||||
|  | ||||
| class ClubAddMemberForm(ClubMemberForm): | ||||
|     """Form to add a member to the club, as a board member.""" | ||||
|  | ||||
|     class Meta(ClubMemberForm.Meta): | ||||
|         fields = ["user", *ClubMemberForm.Meta.fields] | ||||
|         widgets = {"user": AutoCompleteSelectUser} | ||||
|  | ||||
|     @cached_property | ||||
|     def max_available_role(self): | ||||
|         """The greatest role that will be obtainable with this form. | ||||
|  | ||||
|         Admins and the club president can attribute any role. | ||||
|         Board members can attribute roles lower than their own. | ||||
|         Other users can attribute curious and member roles. | ||||
|         Other users cannot attribute roles with this form | ||||
|         """ | ||||
|         if self.request_user.has_perm("club.add_subscription"): | ||||
|             return settings.SITH_CLUB_ROLES_ID["President"] | ||||
|         membership = self.request_user_membership | ||||
|         if membership is not None and membership.role > settings.SITH_MAXIMUM_FREE_ROLE: | ||||
|             if membership.role == settings.SITH_CLUB_ROLES_ID["President"]: | ||||
|                 return membership.role | ||||
|             return membership.role - 1 | ||||
|         return settings.SITH_MAXIMUM_FREE_ROLE | ||||
|         if membership is None or membership.role <= settings.SITH_MAXIMUM_FREE_ROLE: | ||||
|             return -1 | ||||
|         if membership.role == settings.SITH_CLUB_ROLES_ID["President"]: | ||||
|             return membership.role | ||||
|         return membership.role - 1 | ||||
|  | ||||
|     def clean_user(self): | ||||
|         """Check that the user is not trying to add a user already in the club. | ||||
| @@ -264,18 +277,26 @@ class ClubMemberForm(forms.ModelForm): | ||||
|             ) | ||||
|         return user | ||||
|  | ||||
|  | ||||
| class JoinClubForm(ClubMemberForm): | ||||
|     """Form to join a club.""" | ||||
|  | ||||
|     def __init__(self, *args, club: Club, request_user: User, **kwargs): | ||||
|         super().__init__(*args, club=club, request_user=request_user, **kwargs) | ||||
|         self.instance.user = self.request_user | ||||
|  | ||||
|     @cached_property | ||||
|     def max_available_role(self): | ||||
|         return settings.SITH_MAXIMUM_FREE_ROLE | ||||
|  | ||||
|     def clean(self): | ||||
|         """Check user rights for adding a user.""" | ||||
|         cleaned_data = super().clean() | ||||
|         if ( | ||||
|             self.request_user_membership is None | ||||
|             or self.request_user_membership.role <= settings.SITH_MAXIMUM_FREE_ROLE | ||||
|         ) and not self.request_user.has_perm("club.add_membership"): | ||||
|         """Check that the user is subscribed and isn't already in the club.""" | ||||
|         if not self.request_user.is_subscribed: | ||||
|             raise forms.ValidationError( | ||||
|                 _( | ||||
|                     "You cannot add other users to a club " | ||||
|                     "if you are not in the club board." | ||||
|                 ), | ||||
|                 code="invalid", | ||||
|                 _("You must be subscribed to join a club"), code="invalid" | ||||
|             ) | ||||
|         return cleaned_data | ||||
|         if self.club.get_membership_for(self.request_user): | ||||
|             raise forms.ValidationError( | ||||
|                 _("You are already a member of this club"), code="invalid" | ||||
|             ) | ||||
|         return super().clean() | ||||
|   | ||||
| @@ -6,23 +6,22 @@ | ||||
| {% endblock %} | ||||
| {% block additional_css %} | ||||
|   <link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}"> | ||||
|   <link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}"> | ||||
|   <link rel="stylesheet" href="{{ static("club/members.scss") }}"> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
|   {% block notifications %} | ||||
|     {# Notifications are moved inside the billing info fragment #} | ||||
|     {# Notifications are moved a little bit below #} | ||||
|   {% endblock %} | ||||
|  | ||||
|   <h2>{% trans %}Club members{% endtrans %}</h2> | ||||
|  | ||||
|   {% if add_member_fragment %} | ||||
|     <br /> | ||||
|     <h4>{% trans %}Add a new member{% endtrans %}</h4> | ||||
|     {{ add_member_fragment }} | ||||
|     <br /> | ||||
|   {% endif %} | ||||
|   {% include "core/base/notifications.jinja" %} | ||||
|   {% if members %} | ||||
|     <form action="{{ url('club:club_members', club_id=club.id) }}" id="members_old" method="post"> | ||||
|       {% csrf_token %} | ||||
|   | ||||
| @@ -1,32 +1,46 @@ | ||||
| <section id="member-fragment-container"> | ||||
|   {% if form.user %} | ||||
|     <h4>{% trans %}Add a new member{% endtrans %}</h4> | ||||
|   {% else %} | ||||
|     <h4>{% trans %}Join club{% endtrans %}</h4> | ||||
|   {% endif %} | ||||
|  | ||||
| {% include "core/base/notifications.jinja" %} | ||||
|  | ||||
| <form | ||||
|   hx-post="{{ url('club:club_new_members', club_id=club.id) }}" | ||||
|   hx-disabled-elt="find input[type='submit']" | ||||
|   hx-swap="outerHTML" | ||||
|   id="add_club_members_form" | ||||
| > | ||||
|   {% csrf_token %} | ||||
|   {{ form.non_field_errors() }} | ||||
|   <fieldset> | ||||
|     <div> | ||||
|       {{ form.user.label_tag()}} | ||||
|       <span class="helptext">{{ form.user.help_text }}</span> | ||||
|       {{ form.user }} | ||||
|       {{ form.user.errors }} | ||||
|     </div> | ||||
|     <div> | ||||
|       {{ form.role.label_tag()}} | ||||
|       {{ form.role }} | ||||
|       {{ form.role.errors }} | ||||
|     </div> | ||||
|     <div> | ||||
|       {{ form.description.label_tag()}} | ||||
|       {{ form.description }} | ||||
|       {{ form.description.errors }} | ||||
|     </div> | ||||
|   </fieldset> | ||||
|   <button type="submit" class="btn btn-blue"> | ||||
|     <i class="fa fa-user-plus"></i> {% trans %}Add{% endtrans %}</button> | ||||
| </form> | ||||
|   <form | ||||
|     hx-post="{{ url('club:club_new_members', club_id=club.id) }}" | ||||
|     hx-disabled-elt="find input[type='submit']" | ||||
|     hx-swap="outerHTML" | ||||
|     hx-target="#member-fragment-container" | ||||
|     id="add_club_members_form" | ||||
|   > | ||||
|     {% csrf_token %} | ||||
|     {{ form.non_field_errors() }} | ||||
|     <fieldset> | ||||
|       {% if form.user %} | ||||
|         <div> | ||||
|           {{ form.user.label_tag() }} | ||||
|           <span class="helptext">{{ form.user.help_text }}</span> | ||||
|           {{ form.user }} | ||||
|           {{ form.user.errors }} | ||||
|         </div> | ||||
|       {% endif %} | ||||
|       <div> | ||||
|         {{ form.role.label_tag() }} | ||||
|         {{ form.role }} | ||||
|         {{ form.role.errors }} | ||||
|       </div> | ||||
|       <div> | ||||
|         {{ form.description.label_tag() }} | ||||
|         {{ form.description }} | ||||
|         {{ form.description.errors }} | ||||
|       </div> | ||||
|     </fieldset> | ||||
|     <button type="submit" class="btn btn-blue"> | ||||
|       <i class="fa fa-user-plus"></i> | ||||
|       {%- if club.user -%} | ||||
|         {% trans %}Add{% endtrans %} | ||||
|       {%- else -%} | ||||
|         {% trans %}Join{% endtrans %} | ||||
|       {%- endif -%} | ||||
|     </button> | ||||
|   </form> | ||||
| </section> | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| from collections.abc import Callable | ||||
| from datetime import timedelta | ||||
|  | ||||
| import pytest | ||||
| from bs4 import BeautifulSoup | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.models import Permission | ||||
| @@ -11,7 +13,7 @@ from django.utils.timezone import localdate, localtime, now | ||||
| from model_bakery import baker | ||||
| from pytest_django.asserts import assertRedirects | ||||
|  | ||||
| from club.forms import ClubMemberForm | ||||
| from club.forms import ClubAddMemberForm, JoinClubForm | ||||
| from club.models import Club, Membership | ||||
| from club.tests.base import TestClub | ||||
| from core.baker_recipes import subscriber_user | ||||
| @@ -268,7 +270,7 @@ class TestMembership(TestClub): | ||||
|         cannot be members of clubs. | ||||
|         """ | ||||
|         for user in self.public, self.old_subscriber: | ||||
|             form = ClubMemberForm( | ||||
|             form = ClubAddMemberForm( | ||||
|                 data={"user": user.id, "role": 1}, | ||||
|                 request_user=self.root, | ||||
|                 club=self.club, | ||||
| @@ -308,7 +310,7 @@ class TestMembership(TestClub): | ||||
|         nb_memberships = self.club.members.count() | ||||
|         max_id = User.objects.aggregate(id=Max("id"))["id"] | ||||
|         for members in [max_id + 1], [max_id + 1, self.subscriber.id]: | ||||
|             form = ClubMemberForm( | ||||
|             form = ClubAddMemberForm( | ||||
|                 data={"user": members, "role": 1}, | ||||
|                 request_user=self.root, | ||||
|                 club=self.club, | ||||
| @@ -346,7 +348,7 @@ class TestMembership(TestClub): | ||||
|         """Test that a member of the club member cannot create | ||||
|         a membership with a greater role than its own. | ||||
|         """ | ||||
|         form = ClubMemberForm( | ||||
|         form = ClubAddMemberForm( | ||||
|             data={"user": self.subscriber.id, "role": 10}, | ||||
|             request_user=self.simple_board_member, | ||||
|             club=self.club, | ||||
| @@ -363,7 +365,7 @@ class TestMembership(TestClub): | ||||
|  | ||||
|     def test_add_member_without_role(self): | ||||
|         """Test that trying to add members without specifying their role fails.""" | ||||
|         form = ClubMemberForm( | ||||
|         form = ClubAddMemberForm( | ||||
|             data={"user": self.subscriber.id}, request_user=self.root, club=self.club | ||||
|         ) | ||||
|  | ||||
| @@ -371,7 +373,7 @@ class TestMembership(TestClub): | ||||
|         assert form.errors == {"role": ["Ce champ est obligatoire."]} | ||||
|  | ||||
|     def test_add_member_already_there(self): | ||||
|         form = ClubMemberForm( | ||||
|         form = ClubAddMemberForm( | ||||
|             data={"user": self.simple_board_member, "role": 3}, | ||||
|             request_user=self.root, | ||||
|             club=self.club, | ||||
| @@ -385,17 +387,14 @@ class TestMembership(TestClub): | ||||
|         non_member = subscriber_user.make() | ||||
|         simple_member = baker.make(Membership, club=self.club, role=1).user | ||||
|         for user in non_member, simple_member: | ||||
|             form = ClubMemberForm( | ||||
|             form = ClubAddMemberForm( | ||||
|                 data={"user": subscriber_user.make(), "role": 1}, | ||||
|                 request_user=user, | ||||
|                 club=self.club, | ||||
|             ) | ||||
|             assert not form.is_valid() | ||||
|             assert form.errors == { | ||||
|                 "__all__": [ | ||||
|                     "Vous ne pouvez pas ajouter d'autres utilisateurs " | ||||
|                     "dans un club si vous ne faites pas partie de son bureau." | ||||
|                 ] | ||||
|                 "role": ["Sélectionnez un choix valide. 1 n\u2019en fait pas partie."] | ||||
|             } | ||||
|  | ||||
|     def test_simple_members_dont_see_form_anymore(self): | ||||
| @@ -533,6 +532,57 @@ class TestMembership(TestClub): | ||||
|         assert new_board == initial_board | ||||
|  | ||||
|  | ||||
| @pytest.mark.django_db | ||||
| class TestJoinClub: | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def clear_cache(self): | ||||
|         cache.clear() | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         ("user_factory", "role", "errors"), | ||||
|         [ | ||||
|             ( | ||||
|                 subscriber_user.make, | ||||
|                 2, | ||||
|                 { | ||||
|                     "role": [ | ||||
|                         "Sélectionnez un choix valide. 2 n\u2019en fait pas partie." | ||||
|                     ] | ||||
|                 }, | ||||
|             ), | ||||
|             ( | ||||
|                 lambda: baker.make(User), | ||||
|                 1, | ||||
|                 {"__all__": ["Vous devez être cotisant pour faire partie d'un club"]}, | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_join_club_errors( | ||||
|         self, user_factory: Callable[[], User], role: int, errors: dict | ||||
|     ): | ||||
|         club = baker.make(Club) | ||||
|         user = user_factory() | ||||
|         form = JoinClubForm(club=club, request_user=user, data={"role": role}) | ||||
|         assert not form.is_valid() | ||||
|         assert form.errors == errors | ||||
|  | ||||
|     def test_user_already_in_club(self): | ||||
|         club = baker.make(Club) | ||||
|         user = subscriber_user.make() | ||||
|         baker.make(Membership, user=user, club=club) | ||||
|         form = JoinClubForm(club=club, request_user=user, data={"role": 1}) | ||||
|         assert not form.is_valid() | ||||
|         assert form.errors == {"__all__": ["Vous êtes déjà membre de ce club."]} | ||||
|  | ||||
|     def test_ok(self): | ||||
|         club = baker.make(Club) | ||||
|         user = subscriber_user.make() | ||||
|         form = JoinClubForm(club=club, request_user=user, data={"role": 1}) | ||||
|         assert form.is_valid() | ||||
|         form.save() | ||||
|         assert Membership.objects.ongoing().filter(user=user, club=club).exists() | ||||
|  | ||||
|  | ||||
| class TestOldMembersView(TestCase): | ||||
|     @classmethod | ||||
|     def setUpTestData(cls): | ||||
|   | ||||
| @@ -47,10 +47,11 @@ from django.views.generic import DetailView, ListView, View | ||||
| from django.views.generic.edit import CreateView, DeleteView, UpdateView | ||||
|  | ||||
| from club.forms import ( | ||||
|     ClubAddMemberForm, | ||||
|     ClubAdminEditForm, | ||||
|     ClubEditForm, | ||||
|     ClubMemberForm, | ||||
|     ClubOldMemberForm, | ||||
|     JoinClubForm, | ||||
|     MailingForm, | ||||
|     SellingsForm, | ||||
| ) | ||||
| @@ -266,17 +267,21 @@ class ClubAddMembersFragment( | ||||
|     FragmentMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView | ||||
| ): | ||||
|     template_name = "club/fragments/add_member.jinja" | ||||
|     form_class = ClubMemberForm | ||||
|     model = Membership | ||||
|     object = None | ||||
|     reload_on_redirect = True | ||||
|     permission_required = "club.view_club" | ||||
|     success_message = _("%(user)s has been added to club.") | ||||
|  | ||||
|     def dispatch(self, *args, **kwargs): | ||||
|         self.club = get_object_or_404(Club, pk=kwargs.get("club_id")) | ||||
|         return super().dispatch(*args, **kwargs) | ||||
|  | ||||
|     def get_form_class(self): | ||||
|         user = self.request.user | ||||
|         if user.has_perm("club.add_membership") or self.club.get_membership_for(user): | ||||
|             return ClubAddMemberForm | ||||
|         return JoinClubForm | ||||
|  | ||||
|     def get_form_kwargs(self): | ||||
|         return super().get_form_kwargs() | { | ||||
|             "request_user": self.request.user, | ||||
| @@ -293,6 +298,11 @@ class ClubAddMembersFragment( | ||||
|     def get_context_data(self, **kwargs): | ||||
|         return super().get_context_data(**kwargs) | {"club": self.club} | ||||
|  | ||||
|     def get_success_message(self, cleaned_data): | ||||
|         if "user" not in cleaned_data or cleaned_data["user"] == self.request.user: | ||||
|             return _("You are now a member of this club.") | ||||
|         return _("%(user)s has been added to club.") % cleaned_data | ||||
|  | ||||
|  | ||||
| class ClubMembersView( | ||||
|     ClubTabsMixin, UseFragmentsMixin, PermissionRequiredMixin, DetailFormView | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-09-25 15:33+0200\n" | ||||
| "POT-Creation-Date: 2025-09-26 17:36+0200\n" | ||||
| "PO-Revision-Date: 2016-07-18\n" | ||||
| "Last-Translator: Maréchal <thomas.girod@utbm.fr\n" | ||||
| "Language-Team: AE info <ae.info@utbm.fr>\n" | ||||
| @@ -174,14 +174,12 @@ msgid "You can not add the same user twice" | ||||
| msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur" | ||||
|  | ||||
| #: club/forms.py | ||||
| msgid "You cannot add other users to a club if you are not in the club board." | ||||
| msgstr "" | ||||
| "Vous ne pouvez pas ajouter d'autres utilisateurs dans un club si vous ne " | ||||
| "faites pas partie de son bureau." | ||||
| msgid "You must be subscribed to join a club" | ||||
| msgstr "Vous devez être cotisant pour faire partie d'un club" | ||||
|  | ||||
| #: club/forms.py sas/forms.py | ||||
| msgid "You do not have the permission to do that" | ||||
| msgstr "Vous n'avez pas la permission de faire cela" | ||||
| #: club/forms.py | ||||
| msgid "You are already a member of this club" | ||||
| msgstr "Vous êtes déjà membre de ce club." | ||||
|  | ||||
| #: club/models.py | ||||
| msgid "slug name" | ||||
| @@ -328,10 +326,6 @@ msgstr "Il n'y a pas de club dans ce site web." | ||||
| msgid "Club members" | ||||
| msgstr "Membres du club" | ||||
|  | ||||
| #: club/templates/club/club_members.jinja | ||||
| msgid "Add a new member" | ||||
| msgstr "Ajouter un nouveau membre" | ||||
|  | ||||
| #: club/templates/club/club_members.jinja | ||||
| #: club/templates/club/club_old_members.jinja | ||||
| #: core/templates/core/user_clubs.jinja | ||||
| @@ -570,12 +564,24 @@ msgstr "" | ||||
| msgid "Save" | ||||
| msgstr "Sauver" | ||||
|  | ||||
| #: club/templates/club/fragments/add_member.jinja | ||||
| msgid "Add a new member" | ||||
| msgstr "Ajouter un nouveau membre" | ||||
|  | ||||
| #: club/templates/club/fragments/add_member.jinja | ||||
| msgid "Join club" | ||||
| msgstr "Rejoindre le club" | ||||
|  | ||||
| #: club/templates/club/fragments/add_member.jinja | ||||
| #: core/templates/core/file_detail.jinja core/views/forms.py | ||||
| #: trombi/templates/trombi/detail.jinja | ||||
| msgid "Add" | ||||
| msgstr "Ajouter" | ||||
|  | ||||
| #: club/templates/club/fragments/add_member.jinja | ||||
| msgid "Join" | ||||
| msgstr "Rejoindre" | ||||
|  | ||||
| #: club/templates/club/mailing.jinja | ||||
| msgid "Mailing lists" | ||||
| msgstr "Mailing listes" | ||||
| @@ -687,6 +693,10 @@ msgstr "Listes de diffusion" | ||||
| msgid "%(user)s has been added to club." | ||||
| msgstr "%(user)s a été ajouté au club." | ||||
|  | ||||
| #: club/views.py | ||||
| msgid "You are now a member of this club." | ||||
| msgstr "Vous êtes maintenant membre de ce club." | ||||
|  | ||||
| #: com/forms.py | ||||
| msgid "Format: 16:9 | Resolution: 1920x1080" | ||||
| msgstr "Format : 16:9 | Résolution : 1920x1080" | ||||
| @@ -4657,6 +4667,10 @@ msgstr "Pas de ban actif" | ||||
| msgid "Add a new album" | ||||
| msgstr "Ajouter un nouvel album" | ||||
|  | ||||
| #: sas/forms.py | ||||
| msgid "You do not have the permission to do that" | ||||
| msgstr "Vous n'avez pas la permission de faire cela" | ||||
|  | ||||
| #: sas/forms.py | ||||
| msgid "Upload images" | ||||
| msgstr "Envoyer les images" | ||||
| @@ -5558,4 +5572,4 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée." | ||||
| #: trombi/views.py | ||||
| #, python-format | ||||
| msgid "Maximum characters: %(max_length)s" | ||||
| msgstr "Nombre de caractères max: %(max_length)s" | ||||
| msgstr "Nombre de caractères max: %(max_length)s" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user