2026-01-17 15:51:11 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
|
from uuid import uuid4
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
2026-01-19 11:40:55 +00:00
|
|
|
from django.db.models import BigAutoField, BooleanField, CASCADE, CharField, DateTimeField, ForeignKey, ManyToManyField, Model, TextField, UUIDField, IntegerField
|
2026-01-17 15:51:11 +00:00
|
|
|
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')
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = _('Organization Membership')
|
|
|
|
|
verbose_name_plural = _('Organization Memberships')
|
|
|
|
|
unique_together = [['user', 'organization']]
|
|
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
2026-01-19 11:40:55 +00:00
|
|
|
return f'{self.user.full_name} - {self.organization.name}'
|
2026-01-17 15:51:11 +00:00
|
|
|
|
|
|
|
|
class OrganizationInvitation(TimeStampMixin, Model):
|
|
|
|
|
|
2026-01-19 11:40:55 +00:00
|
|
|
id = BigAutoField(primary_key = True)
|
2026-01-17 15:51:11 +00:00
|
|
|
token = UUIDField(default = uuid4, unique = True, editable = False)
|
2026-01-19 11:40:55 +00:00
|
|
|
|
2026-01-17 15:51:11 +00:00
|
|
|
organization = ForeignKey(Organization, on_delete = CASCADE, related_name = "invite_tokens")
|
|
|
|
|
created_by = ForeignKey(User, on_delete = CASCADE, related_name = "created_invites")
|
2026-01-19 11:40:55 +00:00
|
|
|
|
2026-01-17 15:51:11 +00:00
|
|
|
expires_at = DateTimeField()
|
2026-01-19 11:40:55 +00:00
|
|
|
|
2026-01-20 02:59:22 +00:00
|
|
|
uses = IntegerField(default = 0)
|
2026-01-19 11:40:55 +00:00
|
|
|
max_uses = IntegerField(default = 1)
|
|
|
|
|
|
2026-01-17 15:51:11 +00:00
|
|
|
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):
|
2026-01-20 02:59:22 +00:00
|
|
|
return self.is_active and self.uses < self.max_uses and timezone.now() < self.expires_at
|
2026-01-17 15:51:11 +00:00
|
|
|
|
|
|
|
|
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)
|
2026-01-19 11:40:55 +00:00
|
|
|
description = TextField(blank = True, default = '')
|
2026-01-17 15:51:11 +00:00
|
|
|
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):
|
|
|
|
|
|
2026-01-19 11:40:55 +00:00
|
|
|
id = BigAutoField(primary_key = True)
|
2026-01-17 15:51:11 +00:00
|
|
|
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}"
|