diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po index 54af1d06f8bdd3847179ecc5f279fc42658d76ca..c61e33aaa95e18a841b493c29b8f5095d3a70738 100644 --- a/src/core/locale/de/LC_MESSAGES/django.po +++ b/src/core/locale/de/LC_MESSAGES/django.po @@ -1170,6 +1170,12 @@ msgstr "Team Mitglied" msgid "Habitat" msgstr "Habitatszuordnung" +msgid "Invitation__name" +msgstr "Einladung" + +msgid "Invitation__name__plural" +msgstr "Einladungen" + msgid "Invitation__comment" msgstr "Kommentar" diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po index dedbe2fa9ed548ca67ebff12a15ad6213600379f..8046fe5a53d0c0501ddfc713e83f40b43f404925 100644 --- a/src/core/locale/en/LC_MESSAGES/django.po +++ b/src/core/locale/en/LC_MESSAGES/django.po @@ -1170,6 +1170,12 @@ msgstr "Team member" msgid "Habitat" msgstr "Habitat assignment" +msgid "Invitation__name" +msgstr "invitation" + +msgid "Invitation__name__plural" +msgstr "invitations" + msgid "Invitation__comment" msgstr "comment" diff --git a/src/core/migrations/0163_alter_invitation_options.py b/src/core/migrations/0163_alter_invitation_options.py new file mode 100644 index 0000000000000000000000000000000000000000..91948ae4b6a21171aed8416c01c8c3ffa697dc9b --- /dev/null +++ b/src/core/migrations/0163_alter_invitation_options.py @@ -0,0 +1,20 @@ +# Generated by Django 5.1.3 on 2024-11-30 12:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0162_remove_workadventure"), + ] + + operations = [ + migrations.AlterModelOptions( + name="invitation", + options={ + "verbose_name": "Invitation__name", + "verbose_name_plural": "Invitation__name__plural", + }, + ), + ] diff --git a/src/core/models/invitation.py b/src/core/models/invitation.py index d25fb39d6181cf6bf5bcab032371d5736923921f..955e1823f1e62da2a702b52dd7da3ae9099134e8 100644 --- a/src/core/models/invitation.py +++ b/src/core/models/invitation.py @@ -18,6 +18,10 @@ if TYPE_CHECKING: # pragma: no cover class InvitationAlreadyExists(ValidationError): + """ + This exception is raised when an invitation already exists between a requester and a requested entity. + """ + def __init__(self, invitation: 'Invitation') -> None: self.invitation = invitation super().__init__( @@ -28,6 +32,10 @@ class InvitationAlreadyExists(ValidationError): class InvitationRejectedTimeout(ValidationError): + """ + This exception is raised when an invitation has been rejected and the timeout has not yet expired. + """ + def __init__(self) -> None: super().__init__( _('Invitation__error__rejected_timeout'), @@ -36,6 +44,16 @@ class InvitationRejectedTimeout(ValidationError): class RequestsState(models.TextChoices): + """ + Enumeration for the state of requests. + + Attributes: + REQUESTED: The request has been made. + WITHDRAWN: The request has been withdrawn. + ACCEPTED: The request has been accepted. + REJECTED: The request has been rejected. + """ + REQUESTED = 'requested', _('Invitation__requests_state-requested') WITHDRAWN = 'withdrawn', _('Invitation__requests_state-withdrawn') ACCEPTED = 'accepted', _('Invitation__requests_state-accepted') @@ -43,18 +61,42 @@ class RequestsState(models.TextChoices): class InvitationType(models.TextChoices): + """ + Enumeration for the type of invitations. + + Attributes: + ASSEMBLY_MEMBER: Invitation for an Conferencemember to join an assembly. + TEAM_MEMBER: Invitation for an Conferencemember to join a team. + HABITAT: Invitation of ah Assembly for a habitat. + """ + ASSEMBLY_MEMBER = 'ASSEMBLY_MEMBER', _('Assembly Member') TEAM_MEMBER = 'TEAM_MEMBER', _('Team Member') HABITAT = 'HABITAT', _('Habitat') class Invitation(models.Model): + """ + Model representing an invitation. + + Attributes: + requester_type: The type of the requester (ContentType). + requester_id: The ID of the requester. + requester: The requester (GenericForeignKey). + requested_type: The type of the requested entity (ContentType). + requested_id: The ID of the requested entity. + requested: The requested entity (GenericForeignKey). + comment: A comment associated with the invitation. + """ + class Meta: indexes = [ models.Index(fields=['requester_id', 'requested_id']), models.Index(fields=['requester_type', 'requester_id'], name='requester_idx'), models.Index(fields=['requested_type', 'requested_id'], name='requested_idx'), ] + verbose_name = _('Invitation__name') + verbose_name_plural = _('Invitation__name__plural') # Make the enums available through the class InvitationType = InvitationType @@ -108,9 +150,20 @@ class Invitation(models.Model): @classmethod def type_is(cls, obj: object) -> TypeIs['Invitation']: + """Check if the object is an Invitation. + + Returns: + TypeIs[Invitation]: True if the object is an Invitation, False otherwise. + """ return isinstance(obj, cls) def clean(self) -> None: + """Validation for the invitation class. + + Raises: + InvitationAlreadyExists: Is raised when an invitation already exists between a requester and a requested entity. + InvitationRejectedTimeout: Is raised when an invitation has been rejected and the timeout has not yet expired. + """ qs = Invitation.objects.filter(requester_id=self.requester_id, requested_id=self.requested_id).exclude(id=self.id) if qs.exists(): if qs.filter(state=RequestsState.REQUESTED).exists(): @@ -123,9 +176,17 @@ class Invitation(models.Model): hours=settings.INVITATION_REJECTION_DEFAULT_TIMEOUT ): raise InvitationRejectedTimeout - return super().clean() + super().clean() def accept(self, user: 'PlatformUser'): + """Accept the invitation. + + Args: + user (PlatformUser): The user who accepts the invitation. + + Raises: + NotImplementedError: Raied when the invitation type is not yet implemented. + """ self.decision_by = user match self.type: case InvitationType.HABITAT: @@ -134,6 +195,11 @@ class Invitation(models.Model): raise NotImplementedError def accept_habitat(self): + """Accept the invitation for a joining a habitat. + + Raises: + ValueError: I raised when the requested or requester type is not a habitat. + """ from core.models import Assembly # pylint: disable=import-outside-toplevel if not Assembly.type_is(self.requested): # pragma: no cover @@ -165,11 +231,21 @@ class Invitation(models.Model): ) def reject(self, user: 'PlatformUser'): + """Reject the invitation. + + Args: + user (PlatformUser): The user rejecting the invitation. + """ self.decision_by = user self.state = RequestsState.REJECTED self.save() def withdraw(self, user: 'PlatformUser'): + """Withdraw the invitation. + + Args: + user (PlatformUser): The user withdrawing the invitation. + """ self.decision_by = user self.state = RequestsState.WITHDRAWN self.save()