Backend: - User.role (user/uploader/staff) with can_upload; uploads and downloads gated to uploader+; owners and staff can edit their items - MediaItem.uploaded_by plus J-<id> identity (serializer, admin, scan_files --user, first superuser as default owner) - API resolves J-<id>, bare numeric ids and MD5s; neighbors and lookup return j_ids - Guest safety: mirror e621's anonymous default blacklist into Redis (parses comments, negations and wildcards), flag hidden_from_guests and filter lists, details and lookups for anonymous users - POST /api/online/downloads/ writes an e621 file into the watched folder and indexes it for the uploader - MariaDB + Redis via docker compose (host ports 3307/6380), PyMySQL driver shim, Redis cache replacing the file cache; SQLite data dumped and loaded into MariaDB Frontend: - Single /detail/:itemId route with an adaptive shell: J-<id> renders the library item, bare numbers render the e621 post - Legacy /view/<md5> and /online/view/<id> redirect to canonical URLs - Cards expose J-IDs; library custom-data editor is read-only for non-owners - Role gating: Upload hidden/blocked for regular users, account shows the role, guest hint on the library
81 lines
2.3 KiB
Python
81 lines
2.3 KiB
Python
import os
|
|
|
|
from rest_framework import serializers
|
|
|
|
from .models import MediaItem, MediaLocation
|
|
from .services import VIDEO_EXTENSIONS
|
|
|
|
|
|
class MediaLocationSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = MediaLocation
|
|
fields = ["id", "path", "rel_path", "mtime"]
|
|
|
|
|
|
class MediaItemSerializer(serializers.ModelSerializer):
|
|
tags = serializers.ListField(
|
|
child=serializers.CharField(max_length=100), required=False
|
|
)
|
|
rating = serializers.ChoiceField(choices=["", "s", "q", "e"], required=False)
|
|
locations = MediaLocationSerializer(many=True, read_only=True)
|
|
j_id = serializers.SerializerMethodField()
|
|
filename = serializers.SerializerMethodField()
|
|
extension = serializers.SerializerMethodField()
|
|
kind = serializers.SerializerMethodField()
|
|
uploaded_by = serializers.SerializerMethodField()
|
|
uploaded_by_id = serializers.IntegerField(read_only=True)
|
|
|
|
class Meta:
|
|
model = MediaItem
|
|
fields = [
|
|
"id",
|
|
"j_id",
|
|
"md5",
|
|
"size",
|
|
"rating",
|
|
"tags",
|
|
"notes",
|
|
"filename",
|
|
"extension",
|
|
"kind",
|
|
"uploaded_by",
|
|
"uploaded_by_id",
|
|
"locations",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
read_only_fields = [
|
|
"id",
|
|
"j_id",
|
|
"md5",
|
|
"size",
|
|
"filename",
|
|
"extension",
|
|
"kind",
|
|
"uploaded_by",
|
|
"uploaded_by_id",
|
|
"locations",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
|
|
def get_j_id(self, obj):
|
|
return f"J-{obj.id}"
|
|
|
|
def get_uploaded_by(self, obj):
|
|
return obj.uploaded_by.username if obj.uploaded_by else None
|
|
|
|
def _first_location(self, obj):
|
|
locations = list(obj.locations.all())
|
|
return locations[0] if locations else None
|
|
|
|
def get_filename(self, obj):
|
|
location = self._first_location(obj)
|
|
return os.path.basename(location.rel_path) if location else obj.md5
|
|
|
|
def get_extension(self, obj):
|
|
return os.path.splitext(self.get_filename(obj))[1].lstrip(".").lower()
|
|
|
|
def get_kind(self, obj):
|
|
return "video" if "." + self.get_extension(obj) in VIDEO_EXTENSIONS else "image"
|