GooDrive/backend/drive/models.py
Louis Mazin 7da4c7cd67 nice
2026-10-14 16:35:14 +02:00

131 lines
7.0 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>).
# Files go under /storage/0001/.../ab/<uuid>, without a /root layer.
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)
class ItemGrant(models.Model):
"""Read-only item/folder access, independent of membership in its space."""
item = models.ForeignKey(DriveItem, on_delete=models.CASCADE, related_name='user_shares')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='shared_drive_items')
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='+')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [models.UniqueConstraint(fields=['item', 'user'], name='drive_item_user_share_unique')]
class PublicItemLink(models.Model):
"""Secret-token link to a single file or directory tree."""
item = models.ForeignKey(DriveItem, on_delete=models.CASCADE, related_name='public_links')
token_hash = models.CharField(max_length=64, unique=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='+')
created_at = models.DateTimeField(auto_now_add=True)
revoked_at = models.DateTimeField(null=True, blank=True)
class ArchivedDriveAccount(models.Model):
"""Account removed from the app while its immutable private space survives."""
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='drive_archive')
deleted_at = models.DateTimeField(auto_now_add=True)