Louis Mazin 71809b6a90 nickel
2026-10-15 13:56:12 +02:00

151 lines
8.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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)
parent_permissions = models.JSONField(null=True, blank=True, default=None)
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)
permissions = models.JSONField(null=True, blank=True, default=None)
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)
permissions = models.JSONField(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 {'.', '..', '.gooddrive-trash'} 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='+')
permissions = models.JSONField(null=True, blank=True, default=None)
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):
"""Inactive, anonymized historical owner of shared files after account removal."""
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='drive_archive')
deleted_at = models.DateTimeField(auto_now_add=True)
class PersistentLoginSession(models.Model):
"""Session explicitly revocable, without an automatic expiration date.
The browser only knows the random secret. Never store that secret in the DB.
"""
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='gooddrive_sessions')
token_hash = models.CharField(max_length=64, unique=True)
auth_hash = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now_add=True)
last_used_at = models.DateTimeField(auto_now_add=True)
revoked_at = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f'Session {self.pk} – utilisateur {self.user_id}'