You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

89 lines
4.2 KiB

import logging
from django.core.management.base import BaseCommand
from apps.bookmark.models import Bookmark
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Find and delete orphan bookmarks whose target content was deleted from the database."
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Scan and report orphan bookmarks without actually deleting them.',
)
def handle(self, *args, **options):
dry_run = options.get('dry_run', False)
from apps.library.models import Book
from apps.podcast.models import Podcast, PodcastPlaylist
from apps.video.models import Video, VideoPlaylist
from apps.hadis.models import Hadis, HadisCorrection
from apps.article.models import Article
service_model_map = {
Bookmark.ServiceChoices.LIBRARY: (Book, 'Library Book'),
Bookmark.ServiceChoices.PODCAST: (Podcast, 'Podcast'),
Bookmark.ServiceChoices.PODCAST_PLAYLIST: (PodcastPlaylist, 'Podcast Playlist'),
Bookmark.ServiceChoices.HADITH: (Hadis, 'Hadith'),
Bookmark.ServiceChoices.HADITH_CORRECTION: (HadisCorrection, 'Hadith Correction'),
Bookmark.ServiceChoices.VIDEO: (Video, 'Video'),
Bookmark.ServiceChoices.VIDEO_PLAYLIST: (VideoPlaylist, 'Video Playlist'),
Bookmark.ServiceChoices.ARTICLE: (Article, 'Article'),
}
total_checked = Bookmark.objects.count()
self.stdout.write(f"Scanning {total_checked} total bookmark(s)...")
total_orphans = 0
orphan_ids_to_delete = []
for service_choice, (model_cls, label) in service_model_map.items():
bms = Bookmark.objects.filter(service=service_choice)
content_ids = set(bms.values_list('content_id', flat=True))
if not content_ids:
continue
if service_choice == Bookmark.ServiceChoices.VIDEO:
existing_ids = set(Video.objects.filter(id__in=content_ids).values_list('id', flat=True)) | set(VideoPlaylist.objects.filter(id__in=content_ids).values_list('id', flat=True))
elif service_choice == Bookmark.ServiceChoices.PODCAST:
existing_ids = set(Podcast.objects.filter(id__in=content_ids).values_list('id', flat=True)) | set(PodcastPlaylist.objects.filter(id__in=content_ids).values_list('id', flat=True))
else:
existing_ids = set(model_cls.objects.filter(id__in=content_ids).values_list('id', flat=True))
missing_ids = content_ids - existing_ids
if missing_ids:
orphan_bms = bms.filter(content_id__in=missing_ids)
count = orphan_bms.count()
total_orphans += count
ids = list(orphan_bms.values_list('id', flat=True))
orphan_ids_to_delete.extend(ids)
self.stdout.write(
self.style.WARNING(f" - [{label} ({service_choice})]: found {count} orphan bookmark(s) (missing IDs: {sorted(list(missing_ids))[:10]}{'...' if len(missing_ids) > 10 else ''})")
)
# Check any bookmarks with unknown service choices
known_services = set(service_model_map.keys())
unknown_bms = Bookmark.objects.exclude(service__in=known_services)
unknown_count = unknown_bms.count()
if unknown_count > 0:
total_orphans += unknown_count
orphan_ids_to_delete.extend(list(unknown_bms.values_list('id', flat=True)))
self.stdout.write(
self.style.WARNING(f" - [Unknown Service]: found {unknown_count} bookmark(s) with invalid service name.")
)
if total_orphans == 0:
self.stdout.write(self.style.SUCCESS("No orphan bookmarks found! Everything is clean."))
return
self.stdout.write(f"\nTotal orphan bookmark(s) found: {total_orphans}")
if dry_run:
self.stdout.write(self.style.NOTICE("Dry run enabled. No bookmarks were deleted."))
else:
deleted_count, _ = Bookmark.objects.filter(id__in=orphan_ids_to_delete).delete()
self.stdout.write(self.style.SUCCESS(f"Successfully deleted {deleted_count} orphan bookmark(s)!"))