GooDrive/backend/drive/models.py
Louis Mazin ac44c6ed8b nice
2026-10-13 23:47:19 +02:00

105 lines
5.6 KiB
Python

"""GoodDrive's logical folders and delegated user spaces.
A DriveSpace is a numbered physical directory. DriveItems remain DB-managed,
so renaming a space never renames its physical path.
"""
import uuid
from django.conf import settings
from django.db import models
from django.core.exceptions import ValidationError
class DriveSpace(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='drive_space', null=True, blank=True)
managed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True, related_name='managed_drive_spaces')
private_owner = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='private_drive_space', null=True, blank=True)
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.PROTECT, related_name='children')
directory_name = models.CharField(max_length=24, unique=True, null=True, blank=True)
relative_path = models.CharField(max_length=700, unique=True)
display_name = models.CharField(max_length=120)
can_view_parent = models.BooleanField(default=True)
can_write_parent = models.BooleanField(default=False)
can_invite = models.BooleanField(default=False)
quota_bytes = models.BigIntegerField(null=True, blank=True, default=None)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.display_name} ({self.relative_path})'
class SpaceGrant(models.Model):
"""Grant access to an EXISTING account, without changing its hierarchy."""
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='granted_spaces')
space = models.ForeignKey(DriveSpace, on_delete=models.CASCADE, related_name='access_grants')
can_write = models.BooleanField(default=False)
can_invite = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [models.UniqueConstraint(fields=['user', 'space'], name='drive_unique_space_grant')]
class SpaceInvitation(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
token_hash = models.CharField(max_length=64, unique=True)
parent_space = models.ForeignKey(DriveSpace, on_delete=models.CASCADE, related_name='invitations')
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
can_view_parent = models.BooleanField(default=True)
can_write_parent = models.BooleanField(default=False)
can_invite = models.BooleanField(default=False)
quota_bytes = models.BigIntegerField(null=True, blank=True, default=None)
expires_at = models.DateTimeField()
used_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='redeemed_invitations')
used_at = models.DateTimeField(null=True, blank=True)
revoked_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class DriveItem(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='drive_items')
space = models.ForeignKey(DriveSpace, on_delete=models.PROTECT, related_name='items')
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, related_name='children')
name = models.CharField(max_length=255)
is_folder = models.BooleanField(default=False)
storage_key = models.CharField(max_length=120, blank=True)
# Existing files keep a blank storage_location (legacy /files/ab/<uuid>).
# New files go under /files/root/0001/.../ab/<uuid>.
storage_location = models.CharField(max_length=1200, blank=True)
size = models.BigIntegerField(default=0)
mime_type = models.CharField(max_length=180, default='application/octet-stream')
is_shared = models.BooleanField(default=False) # legacy; no global sharing in hierarchical mode
is_starred = models.BooleanField(default=False)
trashed_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-is_folder', 'name', 'id']
indexes = [models.Index(fields=['owner','parent']), models.Index(fields=['space','trashed_at'])]
def clean(self):
if not self.name or self.name in {'.', '..'} or any(c in self.name for c in '/\\\x00\r\n'):
raise ValidationError('Nom de fichier ou dossier invalide.')
if len(self.name) > 255:
raise ValidationError('Nom trop long.')
if self.parent_id and not self.parent.is_folder:
raise ValidationError('Le parent doit être un dossier.')
if self.parent_id and self.parent.space_id != self.space_id:
raise ValidationError('Le dossier de destination appartient à un autre espace.')
def __str__(self):
return self.name
class UploadSession(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
space = models.ForeignKey(DriveSpace, on_delete=models.CASCADE)
parent = models.ForeignKey(DriveItem, null=True, blank=True, on_delete=models.SET_NULL)
name = models.CharField(max_length=255)
total_size = models.BigIntegerField()
received = models.BigIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)