From a3cd6be3efdeb0fb0e88339aea76c34b03cb4b40 Mon Sep 17 00:00:00 2001 From: Siebren Bakker Date: Mon, 29 Jun 2026 15:15:08 -0500 Subject: [PATCH 1/2] Allow narrators to re-record flagged recordings Add a "Re-record this book" link on the flagged recording detail page. When a narrator uploads a new recording for a book, any previously flagged recordings for that book are deleted. Flagged recordings no longer count toward max_narrators limits. --- books/views.py | 8 ++++++-- templates/books/recording_detail.html | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/books/views.py b/books/views.py index 4ea261d..f429cb0 100644 --- a/books/views.py +++ b/books/views.py @@ -4,7 +4,7 @@ import re from django.conf import settings -from django.db.models import Count +from django.db.models import Count, Q from django.http import FileResponse, Http404, HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse @@ -108,7 +108,7 @@ def dashboard(request): available_books = ( Book.objects.exclude(id__in=unflagged_book_ids) - .annotate(recording_count=Count("recordings")) + .annotate(recording_count=Count("recordings", filter=Q(recordings__flagged_for_review=False))) ) books_with_availability = [] @@ -220,6 +220,10 @@ def upload_recording(request, book_id): "error": "Something went wrong saving your recording. Please let an admin know." }, status=500) + Recording.objects.filter( + book=book, narrator=narrator, flagged_for_review=True + ).exclude(id=recording.id).delete() + spawn_remux(recording.id) request.session.pop(f"preflight_{book_id}", None) diff --git a/templates/books/recording_detail.html b/templates/books/recording_detail.html index b4a7a1f..9e0d49f 100644 --- a/templates/books/recording_detail.html +++ b/templates/books/recording_detail.html @@ -54,6 +54,9 @@

Your Recording

Flagged for Admin Review

{{ recording.flag_reason }}

+

+ Re-record this book +

{% else %}
From 2f7b074a46df0c2dad5902633aae5f64c1ae6771 Mon Sep 17 00:00:00 2001 From: Siebren Bakker Date: Mon, 29 Jun 2026 15:15:17 -0500 Subject: [PATCH 2/2] Add tests for narrator re-record flow Cover flagged recording dashboard visibility, max_narrators exclusion of flagged recordings, re-record link presence, and cleanup of flagged recordings on re-upload. --- books/tests/test_dashboard.py | 28 ++++++++++++++++++++++++++++ books/tests/test_upload.py | 16 ++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/books/tests/test_dashboard.py b/books/tests/test_dashboard.py index 0f25f07..fa80d34 100644 --- a/books/tests/test_dashboard.py +++ b/books/tests/test_dashboard.py @@ -43,6 +43,34 @@ def test_dashboard_excludes_full_books(self): assert resp.status_code == 200 assert b"No books available for recording right now" in resp.content + def test_flagged_recording_shows_book_available(self): + Recording.objects.create( + book=self.book, narrator=self.narrator, status=RecordingStatus.READY, + flagged_for_review=True, flag_reason="Bad audio", + ) + resp = self.client.get("/portal/") + assert b"Unique Testbook XYZ" in resp.content + + def test_flagged_recording_not_counted_toward_max_narrators(self): + self.book.max_narrators = 1 + self.book.save() + other = Narrator.objects.create(name="Other", email="other@test.com", passphrase="other-p2") + Recording.objects.create( + book=self.book, narrator=other, status=RecordingStatus.READY, + flagged_for_review=True, flag_reason="Noise", + ) + resp = self.client.get("/portal/") + assert b"Unique Testbook XYZ" in resp.content + + def test_flagged_recording_detail_shows_rerecord_link(self): + recording = Recording.objects.create( + book=self.book, narrator=self.narrator, status=RecordingStatus.READY, + flagged_for_review=True, flag_reason="Background noise", + ) + resp = self.client.get(f"/portal/recording/{recording.id}/") + assert b"Re-record this book" in resp.content + assert f"/portal/preflight/{self.book.id}/".encode() in resp.content + def test_invalid_narrator_in_session_redirects(self): client = Client() session = client.session diff --git a/books/tests/test_upload.py b/books/tests/test_upload.py index ce06118..05abc9a 100644 --- a/books/tests/test_upload.py +++ b/books/tests/test_upload.py @@ -142,6 +142,22 @@ def test_successful_upload_creates_recording_and_attestation(self): assert recording.duration_seconds == 120 assert Attestation.objects.filter(recording=recording).exists() + def test_reupload_deletes_flagged_recording(self): + from books.models import RecordingStatus + + flagged = Recording.objects.create( + book=self.book, narrator=self.narrator, status=RecordingStatus.READY, + flagged_for_review=True, flag_reason="Bad audio", + ) + audio = SimpleUploadedFile("test.webm", b"fake audio", content_type="audio/webm") + resp = self.client.post( + f"/portal/upload/{self.book.id}/", + {"audio": audio, "duration": "90", "attestation_text": "I attest again"}, + ) + assert resp.status_code == 200 + assert not Recording.objects.filter(id=flagged.id).exists() + assert Recording.objects.filter(book=self.book, narrator=self.narrator).count() == 1 + def test_upload_requires_login(self): client = Client() audio = SimpleUploadedFile("test.webm", b"fake audio", content_type="audio/webm")