Dynavera/apps/orgs/models.py

137 lines
5.1 KiB
Python

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, IntegerField, FileField
from django.db.models.signals import post_delete
from django.dispatch import receiver
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:
return f'{self.user.full_name} - {self.organization.name}'
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()
uses = IntegerField(default = 0)
max_uses = IntegerField(default = 1)
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 self.uses < self.max_uses 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)
description = TextField(blank = True, default = '')
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):
id = BigAutoField(primary_key = True)
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}"
class TrainingFile(TimeStampMixin, Model):
ALLOWED_EXTENSIONS = ('txt', 'pdf', 'md', 'csv', 'json', 'docx', 'doc')
id = BigAutoField(primary_key = True)
uuid = UUIDField(default = uuid4, unique = True, editable = False)
organization = ForeignKey(Organization, on_delete = CASCADE, related_name = "training_files")
uploaded_by = ForeignKey(User, on_delete = CASCADE, related_name = "uploaded_training_files")
file = FileField(upload_to = 'training_files/%Y/%m/%d/')
file_name = CharField(max_length = 255)
file_size = IntegerField()
file_type = CharField(max_length = 50)
description = TextField(blank = True, default = '')
is_processed = BooleanField(default = False)
class Meta:
verbose_name = _("Training File")
verbose_name_plural = _("Training Files")
ordering = ['-created_at']
def __str__(self) -> str:
return f"{self.file_name} - {self.organization.name}"
@receiver(post_delete, sender=TrainingFile)
def delete_training_file_on_delete(sender, instance, **kwargs):
if instance.file:
try:
import os
if os.path.isfile(instance.file.path):
os.remove(instance.file.path)
except Exception:
pass