from django.utils import timezone from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType import uuid from pathlib import Path from nanoid import generate def generate_unique_slug_for_upload(size=10): while True: slug = generate(size=size) if not Upload.objects.filter(slug=slug).exists(): return slug def upload_to(instance, filename): today = timezone.now().date() dest_path = Path(f"uploads/{str(today.year)}/{str(today.month)}/{str(today.day)}") if not dest_path.exists(): dest_path.mkdir(parents=True) uid = uuid.uuid4().hex file_path = Path(filename) return str(dest_path / f"{uid}{file_path.suffix}") class Upload(models.Model): id = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4) created_at = models.DateTimeField(auto_now_add=True) user = models.ForeignKey('user.User', on_delete=models.CASCADE, null=True, blank=True) filename = models.CharField(max_length=255) slug = models.SlugField(max_length=255, unique=True, default=generate_unique_slug_for_upload, editable=False) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True) object_id = models.UUIDField(null=True, blank=True) content_object = GenericForeignKey('content_type', 'object_id') class AbstractFile(models.Model): file = models.FileField(upload_to=upload_to) content_type = models.CharField(max_length=255) size = models.PositiveBigIntegerField() name = models.CharField(max_length=255) class Meta: abstract = True def save(self, *args, **kwargs): if self.file: from utils import get_mimetype self.size = self.file.size self.content_type = get_mimetype(self.file) self.name = self.file.name class Image(AbstractFile): allowed_mimetypes = [ "image/jpeg", "image/png", "image/gif", "image/webp", "image/bmp", "image/svg+xml", "image/tiff", "image/x-icon", # favicon "image/vnd.microsoft.icon" ] pass class Video(AbstractFile): allowed_mimetypes = [ "video/mp4", "video/quicktime", # .mov "video/x-msvideo", # .avi "video/x-matroska", # .mkv "video/webm", "video/mpeg", "video/ogg" ] pass class Audio(AbstractFile): allowed_mimetypes = [ "audio/mpeg", # .mp3 "audio/wav", # .wav "audio/x-wav", "audio/ogg", # .ogg "audio/webm", # .webm audio "audio/aac", "audio/flac", "audio/x-flac" ] pass class Document(AbstractFile): allowed_mimetypes = [ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", # .docx "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # .xlsx "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", # .pptx "application/rtf", "text/plain", "text/csv" ] pass class Archive(AbstractFile): allowed_mimetypes = [ "application/zip", "application/x-tar", "application/x-gzip", "application/x-bzip2", "application/x-7z-compressed", "application/x-rar-compressed" ] class Text(AbstractFile): allowed_mimetypes = [ "text/plain", # .txt, .log ] pass class Code(AbstractFile): allowed_mimetypes = [ "text/x-python", # .py "text/x-shellscript", # .sh, .bash "text/x-csrc", # .c "text/x-c++src", # .cpp, .cc "text/x-java-source", # .java "text/x-go", # .go "text/x-rustsrc", # .rs "text/x-sql", # .sql "text/x-markdown", # .md "text/markdown", # .md (alternative) "text/x-makefile", # Makefile "text/x-php", # .php "application/javascript", # .js (modern) "text/javascript", # .js (legacy) "text/css", # .css "application/x-perl", # .pl "application/x-ruby", # .rb "application/x-lua", # .lua ] class StructuredData(AbstractFile): allowed_mimetypes = [ "application/json", # .json "application/xml", # .xml "text/xml", # .xml alternative "text/csv", # .csv "text/tab-separated-values", # .tsv "application/x-yaml", # .yaml (rare) "text/x-yaml", # .yaml, .yml (le plus courant) "application/vnd.ms-excel", # .xls (Excel legacy) "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # .xlsx "application/x-hdf", # .hdf, .h5 "application/x-parquet", # .parquet "application/x-ndjson", # .ndjson (newline-delimited JSON) "application/x-netcdf", # .nc ] class Other(AbstractFile): allowed_extensions = [] pass