diff --git a/src/backoffice/forms/assemblies.py b/src/backoffice/forms/assemblies.py index 3cc02fda6f427db7820aae32ef93ba23aa6140ae..227be0f316900f228f34d9ed397f978ed144653a 100644 --- a/src/backoffice/forms/assemblies.py +++ b/src/backoffice/forms/assemblies.py @@ -134,12 +134,3 @@ class AssemblyMemberEditForm(forms.ModelForm): class Meta: model = AssemblyMember fields = ['is_representative', 'can_manage_assembly', 'is_technical_contact', 'show_public'] - - def clean(self): - # call original .clean() which does some checks already - super().clean() - - a = self.instance.assembly - m = self.instance.member - if not self.cleaned_data['can_manage_assembly'] and not a.members.exclude(member=m).filter(can_manage_assembly=True): - self.add_error('can_manage_assembly', _('AssemblyMember__need_a_manager')) diff --git a/src/backoffice/locale/de/LC_MESSAGES/django.po b/src/backoffice/locale/de/LC_MESSAGES/django.po index c675dec3ed8f6acdc3d1af3d9bd19008ff6294cf..08f86f2090567158ea086cd8f8469406a5e2a1ca 100644 --- a/src/backoffice/locale/de/LC_MESSAGES/django.po +++ b/src/backoffice/locale/de/LC_MESSAGES/django.po @@ -26,9 +26,6 @@ msgstr "Dieser Kurzname wird bereits von einer anderen Assembly benutzt." msgid "username" msgstr "Benutzername" -msgid "AssemblyMember__need_a_manager" -msgstr "Mindestens ein Mitglied muss die Assembly verwalten können!" - msgid "AssemblyTeam__message__subject" msgstr "Betreff" @@ -1818,12 +1815,13 @@ msgstr "Das Mitglied \"{user}\" hat sei Ticket nicht aktiviert und kann deshalb msgid "Assembly__members__shown" msgstr "\"{user}\" wird nun öffentlich angezeigt" -msgid "Assembly__members__cant_remove_self" -msgstr "Du kannst dich nicht selbst entfernen!" - msgid "Assembly__members__removed" msgstr "\"{user}\" entfernt" +# Use translation from core +msgid "AssemblyMember__delete__cannot_delete_last_manager" +msgstr "" + msgid "Assembly__members__added" msgstr "\"{user}\" hinzugefügt" diff --git a/src/backoffice/locale/en/LC_MESSAGES/django.po b/src/backoffice/locale/en/LC_MESSAGES/django.po index 70d0d1cef0a8bd616b715d0657b803630cc3f1a6..a07f265e18d6390bfa569a347137506f377137cb 100644 --- a/src/backoffice/locale/en/LC_MESSAGES/django.po +++ b/src/backoffice/locale/en/LC_MESSAGES/django.po @@ -26,9 +26,6 @@ msgstr "This slug is already used by another assembly." msgid "username" msgstr "" -msgid "AssemblyMember__need_a_manager" -msgstr "At least one member must be able to manage the assembly!" - msgid "AssemblyTeam__message__subject" msgstr "subject" @@ -1824,12 +1821,13 @@ msgstr "\"{user}\" can not be shown publicly in this conference as they haven't msgid "Assembly__members__shown" msgstr "\"{user}\" will now be shown publicly" -msgid "Assembly__members__cant_remove_self" -msgstr "You can't remove yourself" - msgid "Assembly__members__removed" msgstr "removed \"{user}\"" +# use translation from core +msgid "AssemblyMember__delete__cannot_delete_last_manager" +msgstr "" + msgid "Assembly__members__added" msgstr "added \"{user}\"" diff --git a/src/backoffice/views/assemblies/members.py b/src/backoffice/views/assemblies/members.py index 5b0e31a976cab65bc9023aa180341c2acd0bafef..594e3b42180106de1990d7ab8fb4b0af7273664a 100644 --- a/src/backoffice/views/assemblies/members.py +++ b/src/backoffice/views/assemblies/members.py @@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _ from django.views.generic import ListView from django.views.generic.edit import FormView, UpdateView +from core.exceptions import LastManagerError from core.models import ActivityLogChange, ActivityLogEntry from core.models.assemblies import AssemblyMember from core.models.users import PlatformUser @@ -92,24 +93,26 @@ class MemberListView(AssemblyMixin, ListView): ) elif k == 'delete': - if int(v) == self.request.user.id and not self.staff_access: - messages.error(self.request, format_lazy(_('Assembly__members__cant_remove_self'))) - continue - m = self.get_queryset().select_related('member').get(member_id=int(v)) - m.delete() - logger.info( - 'removed "%(member)s" from assembly %(assembly)s by %(user)s', {'member': m.member, 'assembly': self.assembly, 'user': self.request.user} - ) - messages.success(self.request, format_lazy(_('Assembly__members__removed'), user=m.member)) - self.assembly.log_activity( - user=self.request.user, - kind=ActivityLogEntry.Kind.ENTITY if not self.staff_access else ActivityLogEntry.Kind.STAFF, - **{ - 'members.removed': ActivityLogChange( - old=m.member.username, - ), - }, - ) + try: + m = self.get_queryset().select_related('member').get(member_id=int(v)) + m.delete() + logger.info( + 'removed "%(member)s" from assembly %(assembly)s by %(user)s', + {'member': m.member, 'assembly': self.assembly, 'user': self.request.user}, + ) + messages.success(self.request, format_lazy(_('Assembly__members__removed'), user=m.member)) + self.assembly.log_activity( + user=self.request.user, + kind=ActivityLogEntry.Kind.ENTITY if not self.staff_access else ActivityLogEntry.Kind.STAFF, + **{ + 'members.removed': ActivityLogChange( + old=m.member.username, + ), + }, + ) + except LastManagerError: + messages.error(self.request, _('AssemblyMember__delete__cannot_delete_last_manager')) + return redirect('backoffice:assembly-members', pk=self.assembly.pk) return redirect('backoffice:assembly-members', pk=self.assembly.pk) diff --git a/src/core/exceptions.py b/src/core/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..9ec0a300cc57518ea3e592297fe146fdbf755f7b --- /dev/null +++ b/src/core/exceptions.py @@ -0,0 +1,5 @@ +from django.core.exceptions import ValidationError + + +class LastManagerError(ValidationError): + pass diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po index 1175719fd8d344f3adf5fb0bc55f6f8c6ee52a96..aee8a920424d3546646e892efd7a84b515d2d475 100644 --- a/src/core/locale/de/LC_MESSAGES/django.po +++ b/src/core/locale/de/LC_MESSAGES/django.po @@ -486,6 +486,12 @@ msgstr "Zugehörigkeit öffentlich anzeigen?" msgid "AssemblyMember__show_public" msgstr "öffentlich" +msgid "AssemblyMember__clean__cannot_remove_last_manager" +msgstr "Dieses Mitglied ist das letzte mit Verwaltungsrechten, sie können nicht entfernt werden." + +msgid "AssemblyMember__delete__cannot_delete_last_manager" +msgstr "Du kannst dieses Mitglied nicht entfernen, da es das letzte Mitglied mit Verwaltungsrechten ist." + msgid "BadgeToken__validator_info__lower_alpha_num" msgstr "Redeem Token beinhalten kleingeschriebene Buchstaben und Nummern." diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po index 9492069223bcb22df38ddabf0ae025211a646e19..b66a8888e0460b2825bbb7051250afd0b57cd999 100644 --- a/src/core/locale/en/LC_MESSAGES/django.po +++ b/src/core/locale/en/LC_MESSAGES/django.po @@ -486,6 +486,12 @@ msgstr "Shall the membership be shown publicly?" msgid "AssemblyMember__show_public" msgstr "is public" +msgid "AssemblyMember__clean__cannot_remove_last_manager" +msgstr "You cannot remove this members rights, as they are the last manager of the team" + +msgid "AssemblyMember__delete__cannot_delete_last_manager" +msgstr "You cannot remove this member, as it is the last manager of the team" + msgid "BadgeToken__validator_info__lower_alpha_num" msgstr "Redeem tokens contain lowercase letters and numbers." diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py index 2f2a97badeb4ffd271ae4bed5040523d7f76dbe5..292948f540dcc0fbc5893ba0d4ba51281dde8625 100644 --- a/src/core/models/assemblies.py +++ b/src/core/models/assemblies.py @@ -2,7 +2,7 @@ import json import logging import re from pathlib import Path -from typing import TypeIs +from typing import Any, TypeIs from uuid import uuid4 import rules @@ -23,6 +23,7 @@ from django.utils.translation import gettext_lazy as _ from rules.contrib.models import RulesModel from rules.predicates import is_authenticated +from core.exceptions import LastManagerError from core.fields import ConferenceReference from core.markdown import compile_translated_markdown_fields, render_markdown, store_relationships from core.models.activitylog import ActivityLogEntry, ActivityLogMixin @@ -647,6 +648,18 @@ class AssemblyMember(models.Model): if self.is_technical_contact: yield 'technical_contact' + def clean(self) -> None: + # Here we clan the new values of the object. So we have to check if it can_manage is False (e.g. remove) + if not self.can_manage_assembly and not self.assembly.members.exclude(member=self.member).filter(can_manage_assembly=True).exists(): + raise LastManagerError(_('AssemblyMember__clean__cannot_remove_last_manager')) + return super().clean() + + def delete(self, using: Any = None, keep_parents: bool = False) -> tuple[int, dict[str, int]]: + # Here we check for the current value of the object. So we have to check if it can_manage is True + if self.can_manage_assembly and not self.assembly.members.exclude(member=self.member).filter(can_manage_assembly=True).exists(): + raise LastManagerError(_('AssemblyMember__delete__cannot_delete_last_manager')) + return super().delete(using, keep_parents) + def get_roles_display(self): res = [] if self.is_representative: diff --git a/src/core/models/teams/team_member.py b/src/core/models/teams/team_member.py index 0fb4ce95bef0d881924c0425c6ef4e14eba820b3..d084fc738bb73aa69409d3d1afeedcaf3c8db9f2 100644 --- a/src/core/models/teams/team_member.py +++ b/src/core/models/teams/team_member.py @@ -1,20 +1,17 @@ from typing import TYPE_CHECKING, Any, TypeIs from uuid import uuid4 -from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import gettext_lazy as _ from rules.contrib.models import RulesModel from rules.predicates import is_superuser, predicate +from core.exceptions import LastManagerError + if TYPE_CHECKING: # pragma: no cover from core.models import PlatformUser -class LastManagerError(ValidationError): - pass - - @predicate def has_invitation(user: 'PlatformUser', team_member: 'TeamMember | None' = None) -> bool: if team_member is None: # pragma: no cover