Source code for certego_saas.apps.organization.invitation

import email_utils
from django.conf import settings
from django.db import models, transaction
from rest_framework.exceptions import ValidationError

from certego_saas.ext.models import TimestampedModel

from .apps import CertegoOrganizationConfig
from .membership import Membership

__all__ = ["Invitation"]


class Status:
    PENDING = "pending"
    ACCEPTED = "accepted"
    DECLINED = "declined"


[docs]class Invitation(TimestampedModel): # Meta Status = Status class Meta: constraints = [ models.constraints.UniqueConstraint( fields=[ "user", "organization", ], condition=models.Q(status=Status.PENDING), name="user_org_pair_single_pending_invite", ) ] # fields user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="invitations" ) organization = models.ForeignKey( f"{CertegoOrganizationConfig.label}.Organization", related_name="invitations", on_delete=models.CASCADE, ) status = models.CharField( max_length=12, choices=[ (Status.PENDING, Status.PENDING), (Status.ACCEPTED, Status.ACCEPTED), (Status.DECLINED, Status.DECLINED), ], default=Status.PENDING, ) # exceptions
[docs] class OwnerException(ValidationError): default_detail = "Cannot create invitation for organization owner."
[docs] class MaxMemberException(ValidationError): default_detail = "Organization reached maximum number of members."
[docs] class AlreadyPresentException(ValidationError): default_detail = "Invite failed. User is already part of the organization."
[docs] class AlreadyPendingException(ValidationError): default_detail = "A similar invite for the user is already pending."
[docs] class PreviouslyDeclinedException(ValidationError): default_detail = "Invitation was previously declined."
[docs] class PreviouslyAcceptedException(ValidationError): default_detail = "Invitation was previously accepted."
# funcs def is_pending(self) -> bool: return self.status == self.Status.PENDING def accept(self) -> None: if self.organization.members_count >= self.organization.MAX_MEMBERS: raise self.MaxMemberException() if self.user.has_membership(): raise Membership.ExistingMembershipException() if self.status == self.Status.DECLINED: raise self.PreviouslyDeclinedException() if self.status == self.Status.ACCEPTED: raise self.PreviouslyAcceptedException() with transaction.atomic(): self.user.membership = Membership.objects.create( user=self.user, organization=self.organization ) self.status = self.Status.ACCEPTED self.user.save() self.save() def decline(self) -> None: if self.status == self.Status.ACCEPTED: raise self.PreviouslyAcceptedException() if self.status == self.Status.DECLINED: raise self.PreviouslyDeclinedException() self.status = self.Status.DECLINED self.save() def email_invite(self, request) -> None: email_utils.send_email( context={ "username": self.user.username, "organization_name": self.organization.name, "organization_owner_username": self.organization.owner.get_username(), "invitation_uri": request.build_absolute_uri("/"), "host_uri": request.build_absolute_uri("/me/invitations"), }, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[self.user.email], subject=f"Certego - Invitation to join {self.organization.name} organization", template_name="certego_saas/emails/org-invitation", ) def __str__(self): return f"Invitation<{self.status}>"