165 lines
4.7 KiB
Python
165 lines
4.7 KiB
Python
from django.db import models
|
|
|
|
from apps.common.models import BaseModel
|
|
|
|
|
|
class MembershipRole(models.TextChoices):
|
|
OWNER = "owner", "Owner"
|
|
ADMIN = "admin", "Admin"
|
|
MEMBER = "member", "Member"
|
|
|
|
|
|
class MembershipStatus(models.TextChoices):
|
|
ACTIVE = "active", "Active"
|
|
INVITED = "invited", "Invited"
|
|
PENDING = "pending", "Pending"
|
|
DEACTIVATED = "deactivated", "Deactivated"
|
|
|
|
|
|
class Membership(BaseModel):
|
|
user = models.ForeignKey(
|
|
"identity.User",
|
|
on_delete=models.CASCADE,
|
|
related_name="memberships",
|
|
)
|
|
organization = models.ForeignKey(
|
|
"organization.Organization",
|
|
on_delete=models.CASCADE,
|
|
related_name="memberships",
|
|
)
|
|
role = models.CharField(
|
|
max_length=16,
|
|
choices=MembershipRole.choices,
|
|
default=MembershipRole.MEMBER,
|
|
)
|
|
status = models.CharField(
|
|
max_length=16,
|
|
choices=MembershipStatus.choices,
|
|
default=MembershipStatus.ACTIVE,
|
|
)
|
|
joined_at = models.DateTimeField(auto_now_add=True)
|
|
ended_at = models.DateTimeField(
|
|
null=True, blank=True, help_text="Membership end date (تاریخ پایان)"
|
|
)
|
|
invited_by = models.ForeignKey(
|
|
"identity.User",
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name="invitations_sent",
|
|
)
|
|
|
|
class Meta:
|
|
ordering = ["-created_at"]
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=["user", "organization"],
|
|
name="unique_membership",
|
|
)
|
|
]
|
|
indexes = [
|
|
models.Index(fields=["user"]),
|
|
models.Index(fields=["organization"]),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.user} @ {self.organization}"
|
|
|
|
|
|
class Ownership(BaseModel):
|
|
"""Organization ownership history and current primary owner."""
|
|
|
|
class OwnershipType(models.TextChoices):
|
|
PRIMARY = "primary", "Primary Owner"
|
|
TRANSFERRED = "transferred", "Transferred Owner"
|
|
|
|
organization = models.ForeignKey(
|
|
"organization.Organization",
|
|
on_delete=models.CASCADE,
|
|
related_name="ownership_history",
|
|
)
|
|
owner = models.ForeignKey(
|
|
"identity.User",
|
|
on_delete=models.CASCADE,
|
|
related_name="ownership_transactions",
|
|
)
|
|
ownership_type = models.CharField(
|
|
max_length=16,
|
|
choices=OwnershipType.choices,
|
|
default=OwnershipType.PRIMARY,
|
|
)
|
|
transferred_from = models.ForeignKey(
|
|
"identity.User",
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name="ownership_transferred_from",
|
|
)
|
|
transferred_at = models.DateTimeField(null=True, blank=True)
|
|
reason = models.CharField(max_length=255, blank=True, default="")
|
|
|
|
class Meta:
|
|
ordering = ["-transferred_at"]
|
|
indexes = [
|
|
models.Index(fields=["organization", "ownership_type"]),
|
|
models.Index(fields=["owner"]),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.get_ownership_type_display()} ownership of {self.organization} by {self.owner}"
|
|
|
|
|
|
class Invitation(BaseModel):
|
|
"""Invitation to join an organization."""
|
|
|
|
class InvitationStatus(models.TextChoices):
|
|
PENDING = "pending", "Pending"
|
|
ACCEPTED = "accepted", "Accepted"
|
|
REJECTED = "rejected", "Rejected"
|
|
EXPIRED = "expired", "Expired"
|
|
|
|
organization = models.ForeignKey(
|
|
"organization.Organization",
|
|
on_delete=models.CASCADE,
|
|
related_name="invitations",
|
|
)
|
|
email = models.EmailField()
|
|
invited_by = models.ForeignKey(
|
|
"identity.User",
|
|
on_delete=models.CASCADE,
|
|
related_name="invitations_created",
|
|
)
|
|
role = models.CharField(
|
|
max_length=16,
|
|
choices=MembershipRole.choices,
|
|
default=MembershipRole.MEMBER,
|
|
)
|
|
token = models.CharField(max_length=64, unique=True)
|
|
status = models.CharField(
|
|
max_length=16,
|
|
choices=InvitationStatus.choices,
|
|
default=InvitationStatus.PENDING,
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
expires_at = models.DateTimeField()
|
|
accepted_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ["-created_at"]
|
|
indexes = [
|
|
models.Index(fields=["organization", "status"]),
|
|
models.Index(fields=["token"]),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Invitation for {self.email} to join {self.organization}"
|
|
|
|
def is_valid(self):
|
|
from django.utils import timezone
|
|
|
|
return (
|
|
self.status == InvitationStatus.PENDING
|
|
and not self.expires_at < timezone.now()
|
|
and self.accepted_at is None
|
|
)
|