2025-12-07 15:23:22 +00:00
|
|
|
from django.db.models import (
|
|
|
|
|
CASCADE,
|
|
|
|
|
CharField,
|
|
|
|
|
FileField,
|
|
|
|
|
ForeignKey,
|
|
|
|
|
UUIDField,
|
|
|
|
|
Model,
|
|
|
|
|
TextField,
|
2025-12-18 23:27:24 +00:00
|
|
|
ManyToManyField,
|
|
|
|
|
DateTimeField,
|
|
|
|
|
BooleanField,
|
|
|
|
|
TextChoices,
|
2025-12-07 15:23:22 +00:00
|
|
|
)
|
2025-12-18 23:27:24 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2025-12-07 15:23:22 +00:00
|
|
|
from uuid import uuid4
|
2025-12-18 23:27:24 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
|
from django.utils import timezone
|
2025-11-18 18:30:10 +00:00
|
|
|
|
2025-12-07 15:23:22 +00:00
|
|
|
from apps.users.models import TimeStampMixin, User
|
2025-11-19 21:44:18 +00:00
|
|
|
|
|
|
|
|
|
2025-12-18 23:27:24 +00:00
|
|
|
class Organization(TimeStampMixin, Model):
|
2025-11-19 21:44:18 +00:00
|
|
|
|
2025-12-18 23:27:24 +00:00
|
|
|
name = CharField(max_length=255, unique=True)
|
|
|
|
|
uuid = UUIDField(default=uuid4, editable=False, 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")
|
2025-12-07 15:23:22 +00:00
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
2025-11-19 21:44:18 +00:00
|
|
|
return self.name
|
|
|
|
|
|
2025-12-07 15:23:22 +00:00
|
|
|
|
2025-12-18 23:27:24 +00:00
|
|
|
class OrganizationMembership(TimeStampMixin, Model):
|
|
|
|
|
|
|
|
|
|
class Role(TextChoices):
|
|
|
|
|
EMPLOYER = "employer", _("Employer")
|
|
|
|
|
EMPLOYEE = "employee", _("Employee")
|
|
|
|
|
|
|
|
|
|
user = ForeignKey(User, on_delete=CASCADE, related_name="organization_memberships")
|
|
|
|
|
organization = ForeignKey(Organization, on_delete=CASCADE, related_name="memberships")
|
|
|
|
|
role = CharField(max_length=50, choices=Role.choices, default=Role.EMPLOYEE)
|
|
|
|
|
|
|
|
|
|
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.role})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InviteToken(TimeStampMixin, Model):
|
|
|
|
|
|
|
|
|
|
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} (expires {self.expires_at})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Domain(Model):
|
|
|
|
|
|
|
|
|
|
name = CharField(max_length=255, unique=True)
|
|
|
|
|
uuid = UUIDField(default=uuid4, editable=False, unique=True)
|
|
|
|
|
description = TextField(blank=True, default="")
|
|
|
|
|
organization = ForeignKey(Organization, on_delete=CASCADE, related_name="domains", null=True, blank=True)
|
|
|
|
|
members = ManyToManyField(User, through="DomainMembership", related_name="domains")
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = _("Domain")
|
|
|
|
|
verbose_name_plural = _("Domains")
|
2025-12-07 15:23:22 +00:00
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
|
return self.name
|
2025-11-19 21:44:18 +00:00
|
|
|
|
2025-12-18 23:27:24 +00:00
|
|
|
|
|
|
|
|
class DomainMembership(TimeStampMixin, Model):
|
|
|
|
|
|
|
|
|
|
user = ForeignKey(User, on_delete=CASCADE, related_name="domain_memberships")
|
|
|
|
|
domain = ForeignKey(Domain, on_delete=CASCADE, related_name="memberships")
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = _("Domain Membership")
|
|
|
|
|
verbose_name_plural = _("Domain Memberships")
|
|
|
|
|
unique_together = [["user", "domain"]]
|
|
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
|
return f"{self.user.full_name} - {self.domain.name}"
|
|
|
|
|
|
2025-12-07 15:23:22 +00:00
|
|
|
class Dataset(TimeStampMixin, Model):
|
2025-11-19 21:44:18 +00:00
|
|
|
|
2025-12-07 15:23:22 +00:00
|
|
|
domain = ForeignKey(Domain, on_delete = CASCADE, related_name = "datasets")
|
|
|
|
|
name = CharField(max_length = 255)
|
|
|
|
|
uuid = UUIDField(default = uuid4, editable = False, unique = True)
|
|
|
|
|
description = TextField(blank = True, default = "")
|
|
|
|
|
created_by = ForeignKey(User, on_delete = CASCADE, related_name = "created_datasets")
|
|
|
|
|
datafile = FileField(upload_to = "datasets/")
|
2025-11-19 21:44:18 +00:00
|
|
|
|
2025-12-07 15:23:22 +00:00
|
|
|
def __str__(self) -> str:
|
2025-12-20 22:33:49 +00:00
|
|
|
return f"{self.name} ({self.domain.name})"
|
|
|
|
|
|
|
|
|
|
Organisation = Organization
|