diff --git a/apps/backups/tests.py b/apps/backups/tests.py index cded0ba4..dc8a5136 100644 --- a/apps/backups/tests.py +++ b/apps/backups/tests.py @@ -1016,6 +1016,41 @@ class BackupSchedulerTestCase(TestCase): scheduler.update_schedule_settings({'enabled': False}) CoreSettings.set_system_time_zone(original_tz) + def test_orphaned_crontab_cleanup(self): + """Test that old CrontabSchedule is deleted when schedule changes""" + from . import scheduler + from django_celery_beat.models import PeriodicTask, CrontabSchedule + + # Create initial daily schedule + scheduler.update_schedule_settings({ + 'enabled': True, + 'frequency': 'daily', + 'time': '03:00', + }) + + task = PeriodicTask.objects.get(name='backup-scheduled-task') + first_crontab_id = task.crontab.id + initial_count = CrontabSchedule.objects.count() + + # Change to weekly schedule (different crontab) + scheduler.update_schedule_settings({ + 'enabled': True, + 'frequency': 'weekly', + 'day_of_week': 3, + 'time': '03:00', + }) + + task.refresh_from_db() + second_crontab_id = task.crontab.id + + # Verify old crontab was deleted + self.assertNotEqual(first_crontab_id, second_crontab_id) + self.assertFalse(CrontabSchedule.objects.filter(id=first_crontab_id).exists()) + self.assertEqual(CrontabSchedule.objects.count(), initial_count) + + # Cleanup + scheduler.update_schedule_settings({'enabled': False}) + class BackupTasksTestCase(TestCase): """Test cases for backup Celery tasks""" diff --git a/frontend/src/components/backups/BackupManager.jsx b/frontend/src/components/backups/BackupManager.jsx index 2376aa14..fed0dcfa 100644 --- a/frontend/src/components/backups/BackupManager.jsx +++ b/frontend/src/components/backups/BackupManager.jsx @@ -618,116 +618,120 @@ export default function BackupManager() { ) : ( - - handleScheduleChange('day_of_week', parseInt(value, 10))} - data={DAYS_OF_WEEK} - disabled={!schedule.enabled} - /> - )} - {is12Hour ? ( - + + { - const hour = displayTime ? displayTime.split(':')[0] : '12'; - handleTimeChange12h(`${hour}:${value}`, null); - }} - data={Array.from({ length: 60 }, (_, i) => ({ - value: String(i).padStart(2, '0'), - label: String(i).padStart(2, '0'), - }))} - disabled={!schedule.enabled} - searchable - /> - handleScheduleChange('day_of_week', parseInt(value, 10))} + data={DAYS_OF_WEEK} + disabled={!schedule.enabled} + /> + )} + {is12Hour ? ( + <> + { + const hour = displayTime ? displayTime.split(':')[0] : '12'; + handleTimeChange12h(`${hour}:${value}`, null); + }} + data={Array.from({ length: 60 }, (_, i) => ({ + value: String(i).padStart(2, '0'), + label: String(i).padStart(2, '0'), + }))} + disabled={!schedule.enabled} + searchable + /> + { + const minute = schedule.time ? schedule.time.split(':')[1] : '00'; + handleTimeChange24h(`${value}:${minute}`); + }} + data={Array.from({ length: 24 }, (_, i) => ({ + value: String(i).padStart(2, '0'), + label: String(i).padStart(2, '0'), + }))} + disabled={!schedule.enabled} + searchable + /> + { - const minute = schedule.time ? schedule.time.split(':')[1] : '00'; - handleTimeChange24h(`${value}:${minute}`); - }} - data={Array.from({ length: 24 }, (_, i) => ({ - value: String(i).padStart(2, '0'), - label: String(i).padStart(2, '0'), - }))} + handleScheduleChange('retention_count', value || 0)} + min={0} disabled={!schedule.enabled} - searchable - /> -