Attribute annotations and scene saves to users
Server-authoritative attribution: on each scene PUT the server diffs incoming annotation groups and stamps createdBy/editedBy (+ timestamps) from request.user, ignoring client-sent stamps so authorship can't be forged. Each save also writes a Revision (user, time, +N ~N −N summary, snapshot of the annotation layer), visible in the admin and via /revisions/. Projects gain collaborators so several users can edit one project and get distinct attribution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
57ef570539
commit
36054ac329
8 changed files with 187 additions and 7 deletions
|
|
@ -1,10 +1,13 @@
|
|||
import json
|
||||
|
||||
from django.db.models import Q
|
||||
from django.http import FileResponse, HttpResponseNotAllowed, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from .models import Project
|
||||
from .attribution import annotation_layer, apply_attribution
|
||||
from .models import Project, Revision
|
||||
|
||||
|
||||
def _auth(request):
|
||||
|
|
@ -15,15 +18,21 @@ def _auth(request):
|
|||
return None
|
||||
|
||||
|
||||
def _visible(request):
|
||||
"""Projects the user owns or collaborates on."""
|
||||
return Project.objects.filter(
|
||||
Q(owner=request.user) | Q(collaborators=request.user)).distinct()
|
||||
|
||||
|
||||
def _owned(request, pk):
|
||||
return get_object_or_404(Project, pk=pk, owner=request.user)
|
||||
return get_object_or_404(_visible(request), pk=pk)
|
||||
|
||||
|
||||
def projects(request):
|
||||
"""GET /api/projects/ — the current user's projects."""
|
||||
"""GET /api/projects/ — the projects the user owns or collaborates on."""
|
||||
if (resp := _auth(request)):
|
||||
return resp
|
||||
rows = Project.objects.filter(owner=request.user).values("id", "name", "fps", "updated")
|
||||
rows = _visible(request).values("id", "name", "fps", "updated", "owner__username")
|
||||
return JsonResponse(list(rows), safe=False)
|
||||
|
||||
|
||||
|
|
@ -37,15 +46,30 @@ def scene(request, pk):
|
|||
return JsonResponse({"fps": project.fps, "scene": project.scene})
|
||||
if request.method == "PUT":
|
||||
data = json.loads(request.body or "{}")
|
||||
summary = None
|
||||
if "scene" in data:
|
||||
project.scene = data["scene"]
|
||||
project.scene, summary, _ = apply_attribution(
|
||||
project.scene, data["scene"], request.user.get_username(),
|
||||
timezone.now().isoformat())
|
||||
Revision.objects.create(project=project, user=request.user,
|
||||
summary=summary,
|
||||
annotations=annotation_layer(project.scene))
|
||||
if "fps" in data:
|
||||
project.fps = data["fps"]
|
||||
project.save(update_fields=["scene", "fps", "updated"])
|
||||
return JsonResponse({"ok": True, "updated": project.updated})
|
||||
return JsonResponse({"ok": True, "updated": project.updated, "summary": summary})
|
||||
return HttpResponseNotAllowed(["GET", "PUT"])
|
||||
|
||||
|
||||
def revisions(request, pk):
|
||||
"""GET /api/projects/<pk>/revisions/ — the save history (who/when/what)."""
|
||||
if (resp := _auth(request)):
|
||||
return resp
|
||||
project = _owned(request, pk)
|
||||
rows = project.revisions.values("id", "user__username", "created", "summary")
|
||||
return JsonResponse(list(rows), safe=False)
|
||||
|
||||
|
||||
def otio(request, pk):
|
||||
"""GET /api/projects/<pk>/otio/ — serve the uploaded OTIO file."""
|
||||
if (resp := _auth(request)):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue