diff --git a/src/core/models/invitation.py b/src/core/models/invitation.py index d25fb39d6181cf6bf5bcab032371d5736923921f..1f5e8925ef63991ceb69325625f776d7f4af6df9 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,12 +61,34 @@ 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']), @@ -108,9 +148,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 +174,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 +193,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 +229,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()