from datetime import timedelta from uuid import uuid4 from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.db.models import BigAutoField, BooleanField, CASCADE, CharField, DateTimeField, ForeignKey, ManyToManyField, Model, TextField, UUIDField from apps.users.mixins import TimeStampMixin from apps.users.models import User class Organization(TimeStampMixin, Model): id = BigAutoField(primary_key = True) uuid = UUIDField(default = uuid4, unique = True, editable = False) name = CharField(max_length = 255, unique = True) description = TextField(blank = True, default = '') owner = ForeignKey(User, on_delete = CASCADE, related_name = 'owned_organizations') members = ManyToManyField(User, through = 'OrganizationMembership', related_name = 'organizations') class Meta: verbose_name = _('Organization') verbose_name_plural = _('Organizations') def __str__(self) -> str: return self.name class OrganizationMembership(TimeStampMixin, Model): id = BigAutoField(primary_key = True) user = ForeignKey(User, on_delete = CASCADE, related_name = 'organization_memberships') organization = ForeignKey(Organization, on_delete = CASCADE, related_name = 'memberships') is_manager = BooleanField(default = False) class Meta: verbose_name = _('Organization Membership') verbose_name_plural = _('Organization Memberships') unique_together = [['user', 'organization']] def __str__(self) -> str: return f'{self.user.full_name} - {self.organization.name} ({self.is_manager})' class OrganizationInvitation(TimeStampMixin, Model): id = BigAutoField(primary_key = True) token = UUIDField(default = uuid4, unique = True, editable = False) organization = ForeignKey(Organization, on_delete = CASCADE, related_name = "invite_tokens") created_by = ForeignKey(User, on_delete = CASCADE, related_name = "created_invites") expires_at = DateTimeField() used_by = ForeignKey(User, on_delete = CASCADE, null = True, blank = True, related_name = "used_invites") used_at = DateTimeField(null = True, blank = True) is_active = BooleanField(default = True) class Meta: verbose_name = _("Invite Token") verbose_name_plural = _("Invite Tokens") def save(self, *args, **kwargs): if not self.expires_at: self.expires_at = timezone.now() + timedelta(days=7) super().save(*args, **kwargs) def is_valid(self): return self.is_active and not self.used_by and timezone.now() < self.expires_at def __str__(self) -> str: return f"Invite for {self.organization.name} by {self.created_by.full_name} (expires {self.expires_at})" class Role(TimeStampMixin, Model): id = BigAutoField(primary_key = True) name = CharField(max_length = 100, unique = True) uuid = UUIDField(default = uuid4, editable = False, unique = True) organization = ForeignKey(Organization, on_delete = CASCADE, related_name = "roles") members = ManyToManyField(User, through = "RoleMembership", related_name = "roles") class Meta: verbose_name = _('Role') verbose_name_plural = _('Roles') def __str__(self) -> str: return self.name class RoleMembership(TimeStampMixin, Model): user = ForeignKey(User, on_delete = CASCADE, related_name = "role_memberships") role = ForeignKey(Role, on_delete = CASCADE, related_name = "memberships") class Meta: verbose_name = _("Role Membership") verbose_name_plural = _("Role Memberships") unique_together = [["user", "role"]] def __str__(self) -> str: return f"{self.user.full_name} - {self.role.name}"