From be4313ba1f17c644d92ce7e5a572d045f49df484 Mon Sep 17 00:00:00 2001 From: Agi-Asi <206806952+Agi-Asi@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:56:58 +0000 Subject: [PATCH] review: optionally queue save_media tasks after bulk actions Adds SAVE_MEDIA_AFTER_BULK_ACTION (env: TUBESYNC_SAVE_MEDIA_AFTER_BULK_ACTION, default False). When enabled, each admin bulk action queues a save_media task per changed item so flags are re-evaluated without waiting for the next source edit or indexing run. When disabled (the default) behaviour is unchanged: re-evaluation happens on the next source edit/index pass. Requested by tcely in review. --- README.md | 1 + tubesync/sync/admin.py | 19 ++++++++++++++ tubesync/sync/tests/test_admin.py | 26 ++++++++++++++++++- tubesync/tubesync/local_settings.py.container | 3 +++ tubesync/tubesync/settings.py | 6 +++++ 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd99d486..51539398 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,7 @@ useful if you are manually installing TubeSync in some other environment. These | TUBESYNC_DIRECTORY_PREFIX | Enable `video` and `audio` directory prefixes in `/downloads` | True | | TUBESYNC_SHRINK_NEW | Filter unneeded information from newly retrieved metadata | True | | TUBESYNC_SHRINK_OLD | Filter unneeded information from metadata loaded from the database | True | +| TUBESYNC_SAVE_MEDIA_AFTER_BULK_ACTION | Queue a `save_media` task per item changed by an admin bulk action, defaults to False | False | | GUNICORN_WORKERS | Number of `gunicorn` (web request) workers to spawn | 3 | | LISTEN_HOST | IP address for `gunicorn` to listen on | 127.0.0.1 | | LISTEN_PORT | Port number for `gunicorn` to listen on | 8080 | diff --git a/tubesync/sync/admin.py b/tubesync/sync/admin.py index 1766e444..ce5be1c1 100644 --- a/tubesync/sync/admin.py +++ b/tubesync/sync/admin.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib import admin from .models import ( Source, @@ -6,6 +7,7 @@ from .models import ( MetadataFormat, MediaServer ) +from .tasks import save_media @admin.register(Source) @@ -31,25 +33,42 @@ class MediaAdmin(admin.ModelAdmin): 'enable_can_download', 'disable_can_download', ) + def _queue_save_media_tasks(self, queryset): + ''' + Optionally queue a `save_media` task per changed item, so flags + are re-evaluated without waiting for the next source edit/index. + Disabled by default: SAVE_MEDIA_AFTER_BULK_ACTION = True enables it. + ''' + if not getattr(settings, 'SAVE_MEDIA_AFTER_BULK_ACTION', False): + return + save_media.map( + str(media_uuid) + for media_uuid in queryset.values_list('uuid', flat=True) + ) + @admin.action(description='Set "skip" for the selected media') def enable_skip(self, request, queryset): updated = queryset.update(skip=True, manual_skip=True) + self._queue_save_media_tasks(queryset) self.message_user(request, f'Set "skip" on {updated} media item(s).') @admin.action(description='Unset "skip" for the selected media') def disable_skip(self, request, queryset): updated = queryset.update(skip=False, manual_skip=False) + self._queue_save_media_tasks(queryset) self.message_user(request, f'Unset "skip" on {updated} media item(s).') @admin.action(description='Set "can download" for the selected media') def enable_can_download(self, request, queryset): updated = queryset.update(can_download=True) + self._queue_save_media_tasks(queryset) self.message_user( request, f'Set "can download" on {updated} media item(s).') @admin.action(description='Unset "can download" for the selected media') def disable_can_download(self, request, queryset): updated = queryset.update(can_download=False) + self._queue_save_media_tasks(queryset) self.message_user( request, f'Unset "can download" on {updated} media item(s).') diff --git a/tubesync/sync/tests/test_admin.py b/tubesync/sync/tests/test_admin.py index 422e96e0..61ba414e 100644 --- a/tubesync/sync/tests/test_admin.py +++ b/tubesync/sync/tests/test_admin.py @@ -1,8 +1,9 @@ import logging +from unittest.mock import patch from django.conf import settings from django.contrib.admin.sites import AdminSite -from django.test import RequestFactory, TestCase +from django.test import RequestFactory, TestCase, override_settings from sync.admin import MediaAdmin from sync.models import Source, Media @@ -84,3 +85,26 @@ class MediaAdminBulkActionsTestCase(TestCase): 'enable_can_download', 'disable_can_download', ): self.assertIn(action, self.model_admin.actions) + + def test_save_media_tasks_not_queued_by_default(self): + queryset = Media.objects.filter(source=self.source) + + with patch('sync.admin.save_media') as mock_save_media: + self.model_admin.enable_skip(self.request, queryset) + + mock_save_media.map.assert_not_called() + + @override_settings(SAVE_MEDIA_AFTER_BULK_ACTION=True) + def test_save_media_tasks_queued_when_enabled(self): + queryset = Media.objects.filter(source=self.source) + + with patch('sync.admin.save_media') as mock_save_media: + self.model_admin.enable_skip(self.request, queryset) + + mock_save_media.map.assert_called_once() + queued = set(mock_save_media.map.call_args.args[0]) + expected = { + str(media_uuid) + for media_uuid in queryset.values_list('uuid', flat=True) + } + self.assertEqual(queued, expected) diff --git a/tubesync/tubesync/local_settings.py.container b/tubesync/tubesync/local_settings.py.container index 85d3e358..cf71170b 100644 --- a/tubesync/tubesync/local_settings.py.container +++ b/tubesync/tubesync/local_settings.py.container @@ -108,6 +108,9 @@ SHRINK_OLD_MEDIA_METADATA = ( 'true' == SHRINK_OLD_MEDIA_METADATA_STR.strip().lo # TUBESYNC_RENAME_ALL_SOURCES: True or False RENAME_ALL_SOURCES_STR = getenv('TUBESYNC_RENAME_ALL_SOURCES', True) RENAME_ALL_SOURCES = ( 'true' == RENAME_ALL_SOURCES_STR.strip().lower() ) +# TUBESYNC_SAVE_MEDIA_AFTER_BULK_ACTION: True or False +SAVE_MEDIA_AFTER_BULK_ACTION_STR = getenv('TUBESYNC_SAVE_MEDIA_AFTER_BULK_ACTION', False) +SAVE_MEDIA_AFTER_BULK_ACTION = ( 'true' == SAVE_MEDIA_AFTER_BULK_ACTION_STR.strip().lower() ) # TUBESYNC_RENAME_SOURCES: A comma-separated list of Source directories RENAME_SOURCES_STR = getenv('TUBESYNC_RENAME_SOURCES') RENAME_SOURCES = RENAME_SOURCES_STR.split(',') if RENAME_SOURCES_STR else list() diff --git a/tubesync/tubesync/settings.py b/tubesync/tubesync/settings.py index 91aadd13..9ac82e42 100644 --- a/tubesync/tubesync/settings.py +++ b/tubesync/tubesync/settings.py @@ -393,6 +393,12 @@ RENAME_ALL_SOURCES = True RENAME_SOURCES = list() +# When True, admin bulk actions on Media queue a `save_media` task for each +# changed item so flags are re-evaluated without waiting for the next source +# edit or indexing run +SAVE_MEDIA_AFTER_BULK_ACTION = False + + # An example for changing the ordering for audio tracks. #ENGLISH_LANGUAGE_CODE_ORDER = ( # 'en-orig',