Backend (Django 6.1 + DRF): - Token auth with a custom User model (register/login/logout/me) - Library models (MediaItem, MediaLocation) and REST endpoints - File list/detail with search, rating filter, sorting, pagination - Multipart upload with optional rating/tags/notes - Range-aware media serving (video seeking) and ffmpeg thumbnails - scan_files management command for the watched folder Frontend (React 19 + Vite + TypeScript): - Catppuccin Mocha design tokens from the design docs - App shell, token persistence, protected routes - Library grid with filters, file detail with custom data editor - Upload page with per-file progress via XHR - Dev proxy to the Django API
39 lines
1.2 KiB
Python
39 lines
1.2 KiB
Python
from django.db import models
|
|
|
|
|
|
class MediaItem(models.Model):
|
|
"""A logical media file, identified by its MD5 fingerprint."""
|
|
|
|
md5 = models.CharField(max_length=32, unique=True)
|
|
size = models.BigIntegerField(default=0)
|
|
# Custom rating: "s" (safe), "q" (questionable), "e" (explicit) or "" (unrated).
|
|
rating = models.CharField(max_length=1, blank=True, default="")
|
|
tags = models.JSONField(default=list, blank=True)
|
|
notes = models.TextField(blank=True, default="")
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ["-created_at"]
|
|
|
|
def __str__(self):
|
|
return self.md5
|
|
|
|
|
|
class MediaLocation(models.Model):
|
|
"""A physical copy of a media item inside the watched folder."""
|
|
|
|
item = models.ForeignKey(
|
|
MediaItem, related_name="locations", on_delete=models.CASCADE
|
|
)
|
|
path = models.CharField(max_length=1024)
|
|
rel_path = models.CharField(max_length=1024)
|
|
mtime = models.FloatField(default=0)
|
|
|
|
class Meta:
|
|
unique_together = ("item", "path")
|
|
ordering = ["rel_path"]
|
|
|
|
def __str__(self):
|
|
return self.rel_path
|